From 9eb0b1f491a1d7f95c03e5a13f36dd7cf66d8a36 Mon Sep 17 00:00:00 2001 From: Joel Stanley Date: Tue, 22 Dec 2020 10:34:51 -0500 Subject: [PATCH 01/31] LiteX: driver for LiteETH network interface Original author: Joel Stanley Added to linux-on-litex-vexriscv by David Shah Polling mode support by Antony Pavlov Updated for 32-bit CSRs and 64bit CPUs by Gabriel Somlo BE fixes & unset-MAC detection by Stafford Horne unused 'struct liteeth *priv' warning by kernel test robot Signed-off-by: Joel Stanley Signed-off-by: Gabriel Somlo --- drivers/net/ethernet/Kconfig | 1 + drivers/net/ethernet/Makefile | 1 + drivers/net/ethernet/litex/Kconfig | 28 ++ drivers/net/ethernet/litex/Makefile | 5 + drivers/net/ethernet/litex/litex_liteeth.c | 369 +++++++++++++++++++++ drivers/net/ethernet/litex/litex_liteeth.h | 88 +++++ 6 files changed, 492 insertions(+) create mode 100644 drivers/net/ethernet/litex/Kconfig create mode 100644 drivers/net/ethernet/litex/Makefile create mode 100644 drivers/net/ethernet/litex/litex_liteeth.c create mode 100644 drivers/net/ethernet/litex/litex_liteeth.h diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index ad04660b97b804..301b431f74bf51 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -115,6 +115,7 @@ config LANTIQ_XRX200 Support for the PMAC of the Gigabit switch (GSWIP) inside the Lantiq / Intel VRX200 VDSL SoC +source "drivers/net/ethernet/litex/Kconfig" source "drivers/net/ethernet/marvell/Kconfig" source "drivers/net/ethernet/mediatek/Kconfig" source "drivers/net/ethernet/mellanox/Kconfig" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index 1e7dc8a7762dcd..07416689972006 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_JME) += jme.o obj-$(CONFIG_KORINA) += korina.o obj-$(CONFIG_LANTIQ_ETOP) += lantiq_etop.o obj-$(CONFIG_LANTIQ_XRX200) += lantiq_xrx200.o +obj-$(CONFIG_NET_VENDOR_LITEX) += litex/ obj-$(CONFIG_NET_VENDOR_MARVELL) += marvell/ obj-$(CONFIG_NET_VENDOR_MEDIATEK) += mediatek/ obj-$(CONFIG_NET_VENDOR_MELLANOX) += mellanox/ diff --git a/drivers/net/ethernet/litex/Kconfig b/drivers/net/ethernet/litex/Kconfig new file mode 100644 index 00000000000000..a78b10c2574c2f --- /dev/null +++ b/drivers/net/ethernet/litex/Kconfig @@ -0,0 +1,28 @@ +# +# LiteX device configuration +# + +config NET_VENDOR_LITEX + bool "LiteX devices" + default y + help + If you have a network (Ethernet) card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about LiteX devices. If you say Y, you will be asked + for your specific card in the following questions. + +if NET_VENDOR_LITEX + +config LITEX_LITEETH + tristate "LiteX Ethernet support" + depends on LITEX + select NET_CORE + select MII + select PHYLIB + help + If you wish to compile a kernel for hardware with a LiteX LiteEth + device then you should answer Y to this. + +endif # NET_VENDOR_LITEX diff --git a/drivers/net/ethernet/litex/Makefile b/drivers/net/ethernet/litex/Makefile new file mode 100644 index 00000000000000..9343b73b8e49da --- /dev/null +++ b/drivers/net/ethernet/litex/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the LiteX network device drivers. +# + +obj-$(CONFIG_LITEX_LITEETH) += litex_liteeth.o diff --git a/drivers/net/ethernet/litex/litex_liteeth.c b/drivers/net/ethernet/litex/litex_liteeth.c new file mode 100644 index 00000000000000..2df6b6369a065c --- /dev/null +++ b/drivers/net/ethernet/litex/litex_liteeth.c @@ -0,0 +1,369 @@ +/* + * LiteX Liteeth Ethernet + * + * Copyright 2017 Joel Stanley + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "litex_liteeth.h" + +#define DRV_NAME "liteeth" +#define DRV_VERSION "0.1" + +#define LITEETH_BUFFER_SIZE 0x800 +#define MAX_PKT_SIZE LITEETH_BUFFER_SIZE + +struct liteeth { + void __iomem *base; + void __iomem *mdio_base; + struct net_device *netdev; + int use_polling; + struct timer_list poll_timer; + struct device *dev; + struct mii_bus *mii_bus; + + /* Link management */ + int cur_duplex; + int cur_speed; + + /* Tx */ + int tx_slot; + int num_tx_slots; + void __iomem *tx_base; + + /* Rx */ + int rx_slot; + int num_rx_slots; + void __iomem *rx_base; +}; + + +static int liteeth_rx(struct net_device *netdev) +{ + struct liteeth *priv = netdev_priv(netdev); + struct sk_buff *skb; + unsigned char *data; + u8 rx_slot; + int len; + + rx_slot = litex_read8(priv->base + LITEETH_WRITER_SLOT_OFF); + len = litex_read32(priv->base + LITEETH_WRITER_LENGTH_OFF); + + skb = netdev_alloc_skb(netdev, len + NET_IP_ALIGN); + if (!skb) { + netdev_err(netdev, "couldn't get memory"); + netdev->stats.rx_dropped++; + return NET_RX_DROP; + } + + /* Ensure alignemnt of the ip header within the skb */ + skb_reserve(skb, NET_IP_ALIGN); + if (len == 0 || len > 2048) + return NET_RX_DROP; + data = skb_put(skb, len); + memcpy_fromio(data, priv->rx_base + rx_slot * LITEETH_BUFFER_SIZE, len); + skb->protocol = eth_type_trans(skb, netdev); + + netdev->stats.rx_packets++; + netdev->stats.rx_bytes += len; + + return netif_rx(skb); +} + +static irqreturn_t liteeth_interrupt(int irq, void *dev_id) +{ + struct net_device *netdev = dev_id; + struct liteeth *priv = netdev_priv(netdev); + u8 reg; + + reg = litex_read8(priv->base + LITEETH_READER_EV_PENDING_OFF); + if (reg) { + netdev->stats.tx_packets++; + litex_write8(priv->base + LITEETH_READER_EV_PENDING_OFF, reg); + } + + reg = litex_read8(priv->base + LITEETH_WRITER_EV_PENDING_OFF); + if (reg) { + liteeth_rx(netdev); + litex_write8(priv->base + LITEETH_WRITER_EV_PENDING_OFF, reg); + } + + return IRQ_HANDLED; +} + +static void liteeh_timeout(struct timer_list *t) +{ + struct liteeth *priv = from_timer(priv, t, poll_timer); + + liteeth_interrupt(0, priv->netdev); + mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(10)); +} + +static int liteeth_open(struct net_device *netdev) +{ + struct liteeth *priv = netdev_priv(netdev); + int err; + + /* TODO: Remove these once we have working mdio support */ + priv->cur_duplex = DUPLEX_FULL; + priv->cur_speed = SPEED_100; + netif_carrier_on(netdev); + + if (!priv->use_polling) { + err = request_irq(netdev->irq, liteeth_interrupt, 0, netdev->name, netdev); + if (err) { + netdev_err(netdev, "failed to request irq %d\n", netdev->irq); + goto err_irq; + } + } + + /* Clear pending events? */ + litex_write8(priv->base + LITEETH_WRITER_EV_PENDING_OFF, 1); + litex_write8(priv->base + LITEETH_READER_EV_PENDING_OFF, 1); + + if (!priv->use_polling) { + /* Enable IRQs? */ + litex_write8(priv->base + LITEETH_WRITER_EV_ENABLE_OFF, 1); + litex_write8(priv->base + LITEETH_READER_EV_ENABLE_OFF, 1); + } + + netif_start_queue(netdev); + + if (priv->use_polling) { + timer_setup(&priv->poll_timer, liteeh_timeout, 0); + mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(50)); + } + + return 0; + +err_irq: + netif_carrier_off(netdev); + return err; +} + +static int liteeth_stop(struct net_device *netdev) +{ + struct liteeth *priv = netdev_priv(netdev); + + netif_stop_queue(netdev); + + del_timer_sync(&priv->poll_timer); + + litex_write8(priv->base + LITEETH_WRITER_EV_ENABLE_OFF, 0); + litex_write8(priv->base + LITEETH_READER_EV_ENABLE_OFF, 0); + + if (!priv->use_polling) { + free_irq(netdev->irq, netdev); + } + + return 0; +} + +static int liteeth_start_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct liteeth *priv = netdev_priv(netdev); + void *txbuffer; + int ret; + u8 val; + + /* Reject oversize packets */ + if (unlikely(skb->len > MAX_PKT_SIZE)) { + if (net_ratelimit()) + netdev_dbg(netdev, "tx packet too big\n"); + goto drop; + } + + txbuffer = priv->tx_base + priv->tx_slot * LITEETH_BUFFER_SIZE; + memcpy_fromio(txbuffer, skb->data, skb->len); + litex_write8(priv->base + LITEETH_READER_SLOT_OFF, priv->tx_slot); + litex_write16(priv->base + LITEETH_READER_LENGTH_OFF, skb->len); + + ret = readx_poll_timeout_atomic(litex_read8, + priv->base + LITEETH_READER_READY_OFF, + val, val, 5, 1000); + if (ret == -ETIMEDOUT) { + netdev_err(netdev, "LITEETH_READER_READY timed out\n"); + goto drop; + } + + litex_write8(priv->base + LITEETH_READER_START_OFF, 1); + + priv->tx_slot = (priv->tx_slot + 1) % priv->num_tx_slots; + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; +drop: + /* Drop the packet */ + dev_kfree_skb_any(skb); + netdev->stats.tx_dropped++; + + return NETDEV_TX_OK; +} + +static void liteeth_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_VERSION, sizeof(info->version)); + strlcpy(info->bus_info, dev_name(&netdev->dev), sizeof(info->bus_info)); +} + +static const struct net_device_ops liteeth_netdev_ops = { + .ndo_open = liteeth_open, + .ndo_stop = liteeth_stop, + .ndo_start_xmit = liteeth_start_xmit, +}; + +static const struct ethtool_ops liteeth_ethtool_ops = { + .get_drvinfo = liteeth_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, + .nway_reset = phy_ethtool_nway_reset, +}; + +static void liteeth_reset_hw(struct liteeth *priv) +{ + /* Reset, twice */ + litex_write8(priv->base + LITEETH_PHY_CRG_RESET_OFF, 0); + udelay(10); + litex_write8(priv->base + LITEETH_PHY_CRG_RESET_OFF, 1); + udelay(10); + litex_write8(priv->base + LITEETH_PHY_CRG_RESET_OFF, 0); + udelay(10); +} + +static int liteeth_probe(struct platform_device *pdev) +{ + struct net_device *netdev; + void __iomem *buf_base; + struct resource *res; + struct liteeth *priv; + const char *mac_addr; + int irq, err; + + netdev = alloc_etherdev(sizeof(*priv)); + if (!netdev) + return -ENOMEM; + + priv = netdev_priv(netdev); + priv->netdev = netdev; + priv->dev = &pdev->dev; + + priv->use_polling = 0; + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ, using polling\n"); + priv->use_polling = 1; + irq = 0; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) { + err = PTR_ERR(priv->base); + goto err; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + priv->mdio_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->mdio_base)) { + err = PTR_ERR(priv->mdio_base); + goto err; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + buf_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(buf_base)) { + err = PTR_ERR(buf_base); + goto err; + } + + err = of_property_read_u32(pdev->dev.of_node, "rx-fifo-depth", + &priv->num_rx_slots); + if (err) { + dev_err(&pdev->dev, "unable to get rx-fifo-depth\n"); + goto err; + } + + err = of_property_read_u32(pdev->dev.of_node, "tx-fifo-depth", + &priv->num_tx_slots); + if (err) { + dev_err(&pdev->dev, "unable to get tx-fifo-depth\n"); + goto err; + } + + /* Rx slots */ + priv->rx_base = buf_base; + priv->rx_slot = 0; + + /* Tx slots come after Rx slots */ + priv->tx_base = buf_base + priv->num_rx_slots * LITEETH_BUFFER_SIZE; + priv->tx_slot = 0; + + mac_addr = of_get_mac_address(pdev->dev.of_node); + if (mac_addr && !IS_ERR(mac_addr) && is_valid_ether_addr(mac_addr)) + memcpy(netdev->dev_addr, mac_addr, ETH_ALEN); + else + eth_hw_addr_random(netdev); + + SET_NETDEV_DEV(netdev, &pdev->dev); + platform_set_drvdata(pdev, netdev); + + netdev->netdev_ops = &liteeth_netdev_ops; + netdev->ethtool_ops = &liteeth_ethtool_ops; + netdev->irq = irq; + + liteeth_reset_hw(priv); + + err = register_netdev(netdev); + if (err) { + dev_err(&pdev->dev, "Failed to register netdev\n"); + goto err; + } + + netdev_info(netdev, "irq %d, mapped at %px\n", netdev->irq, priv->base); + + return 0; +err: + free_netdev(netdev); + return err; +} + +static int liteeth_remove(struct platform_device *pdev) +{ + struct net_device *netdev; + + netdev = platform_get_drvdata(pdev); + unregister_netdev(netdev); + free_netdev(netdev); + return 0; +} + +static const struct of_device_id liteeth_of_match[] = { + { .compatible = "litex,liteeth" }, + { } +}; +MODULE_DEVICE_TABLE(of, liteeth_of_match); + +static struct platform_driver liteeth_driver = { + .probe = liteeth_probe, + .remove = liteeth_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = liteeth_of_match, + }, +}; +module_platform_driver(liteeth_driver); + +MODULE_AUTHOR("Joel Stanley "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/litex/litex_liteeth.h b/drivers/net/ethernet/litex/litex_liteeth.h new file mode 100644 index 00000000000000..d176526492478b --- /dev/null +++ b/drivers/net/ethernet/litex/litex_liteeth.h @@ -0,0 +1,88 @@ +#ifndef _LITEX_LITEETH_H_ +#define _LITEX_LITEETH_H_ + +#include + +/* register sizes */ +#define LITEETH_WRITER_SLOT_SIZE 1 +#define LITEETH_WRITER_LENGTH_SIZE 4 +#define LITEETH_WRITER_ERRORS_SIZE 4 +#define LITEETH_WRITER_EV_STATUS_SIZE 1 +#define LITEETH_WRITER_EV_PENDING_SIZE 1 +#define LITEETH_WRITER_EV_ENABLE_SIZE 1 +#define LITEETH_READER_START_SIZE 1 +#define LITEETH_READER_READY_SIZE 1 +#define LITEETH_READER_LEVEL_SIZE 1 +#define LITEETH_READER_SLOT_SIZE 1 +#define LITEETH_READER_LENGTH_SIZE 2 +#define LITEETH_READER_EV_STATUS_SIZE 1 +#define LITEETH_READER_EV_PENDING_SIZE 1 +#define LITEETH_READER_EV_ENABLE_SIZE 1 +#define LITEETH_PREAMBLE_CRC_SIZE 1 +#define LITEETH_PREAMBLE_ERRORS_SIZE 4 +#define LITEETH_CRC_ERRORS_SIZE 4 + +#define LITEETH_PHY_CRG_RESET_SIZE 1 +#define LITEETH_MDIO_W_SIZE 1 +#define LITEETH_MDIO_R_SIZE 1 + +/* register offsets */ +#define LITEETH_WRITER_SLOT_OFF 0x00 +#define LITEETH_WRITER_LENGTH_OFF \ + _next_reg_off(LITEETH_WRITER_SLOT_OFF, \ + LITEETH_WRITER_SLOT_SIZE) +#define LITEETH_WRITER_ERRORS_OFF \ + _next_reg_off(LITEETH_WRITER_LENGTH_OFF, \ + LITEETH_WRITER_LENGTH_SIZE) +#define LITEETH_WRITER_EV_STATUS_OFF \ + _next_reg_off(LITEETH_WRITER_ERRORS_OFF, \ + LITEETH_WRITER_ERRORS_SIZE) +#define LITEETH_WRITER_EV_PENDING_OFF \ + _next_reg_off(LITEETH_WRITER_EV_STATUS_OFF, \ + LITEETH_WRITER_EV_STATUS_SIZE) +#define LITEETH_WRITER_EV_ENABLE_OFF \ + _next_reg_off(LITEETH_WRITER_EV_PENDING_OFF, \ + LITEETH_WRITER_EV_PENDING_SIZE) +#define LITEETH_READER_START_OFF \ + _next_reg_off(LITEETH_WRITER_EV_ENABLE_OFF, \ + LITEETH_WRITER_EV_ENABLE_SIZE) +#define LITEETH_READER_READY_OFF \ + _next_reg_off(LITEETH_READER_START_OFF, \ + LITEETH_READER_START_SIZE) +#define LITEETH_READER_LEVEL_OFF \ + _next_reg_off(LITEETH_READER_READY_OFF, \ + LITEETH_READER_READY_SIZE) +#define LITEETH_READER_SLOT_OFF \ + _next_reg_off(LITEETH_READER_LEVEL_OFF, \ + LITEETH_READER_LEVEL_SIZE) +#define LITEETH_READER_LENGTH_OFF \ + _next_reg_off(LITEETH_READER_SLOT_OFF, \ + LITEETH_READER_SLOT_SIZE) +#define LITEETH_READER_EV_STATUS_OFF \ + _next_reg_off(LITEETH_READER_LENGTH_OFF, \ + LITEETH_READER_LENGTH_SIZE) +#define LITEETH_READER_EV_PENDING_OFF \ + _next_reg_off(LITEETH_READER_EV_STATUS_OFF, \ + LITEETH_READER_EV_STATUS_SIZE) +#define LITEETH_READER_EV_ENABLE_OFF \ + _next_reg_off(LITEETH_READER_EV_PENDING_OFF, \ + LITEETH_READER_EV_PENDING_SIZE) +#define LITEETH_PREAMBLE_CRC_OFF \ + _next_reg_off(LITEETH_READER_EV_ENABLE_OFF, \ + LITEETH_READER_EV_ENABLE_SIZE) +#define LITEETH_PREAMBLE_ERRORS_OFF \ + _next_reg_off(LITEETH_PREAMBLE_CRC_OFF, \ + LITEETH_PREAMBLE_CRC_SIZE) +#define LITEETH_CRC_ERRORS_OFF \ + _next_reg_off(LITEETH_PREAMBLE_ERRORS_OFF, \ + LITEETH_PREAMBLE_ERRORS_SIZE) + +#define LITEETH_PHY_CRG_RESET_OFF 0x00 +#define LITEETH_MDIO_W_OFF \ + _next_reg_off(LITEETH_PHY_CRG_RESET_OFF, \ + LITEETH_PHY_CRG_RESET_SIZE) +#define LITEETH_MDIO_R_OFF \ + _next_reg_off(LITEETH_MDIO_W_OFF, \ + LITEETH_MDIO_W_SIZE) + +#endif /* _LITEX_LITEETH_H_ */ From fddb0b1a878598b4d695a5ef86e259f20581c726 Mon Sep 17 00:00:00 2001 From: Filip Kokosinski Date: Tue, 22 Dec 2020 11:24:43 -0500 Subject: [PATCH 02/31] LiteX: driver for LiteSPI Original author: Filip Kokosinski Cleanup, 32-bit CSR, 64-bit CPU update: Gabriel Somlo Signed-off-by: Filip Kokosinski Signed-off-by: Gabriel Somlo --- .../devicetree/bindings/spi/litex,litespi.txt | 19 ++ drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/spi-litespi.c | 206 ++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/litex,litespi.txt create mode 100644 drivers/spi/spi-litespi.c diff --git a/Documentation/devicetree/bindings/spi/litex,litespi.txt b/Documentation/devicetree/bindings/spi/litex,litespi.txt new file mode 100644 index 00000000000000..d5ddb4ec6ce493 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/litex,litespi.txt @@ -0,0 +1,19 @@ +LiteSPI controller + +Required properties: +- compatible should be "litex,litespi" +- reg: base address and length of the register set for this device +- litespi,max-bpw: maximum value of bits per word +- litespi,sck-frequency: SPI clock frequency +- litespi,num-cs: number of chip select lines available + +Example: + +litespi0: spi@f0005800 { + compatible = "litex,litespi"; + reg = <0xf0005800 0x100>; + + litespi,max-bpw = <8>; + litespi,sck-frequency = <1000000>; + litespi,num-cs = <1>; +}; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 09a263cf4ae2ed..3d0882e4eee365 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -387,6 +387,13 @@ config SPI_JCORE This enables support for the SPI master controller in the J-Core synthesizable, open source SoC. +config SPI_LITESPI + tristate "LiteSPI SPI master driver" + depends on OF && LITEX + help + This enables support for LiteSPI, the SPI master controller of + the LiteX FPGA SoC builder. + config SPI_LM70_LLP tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)" depends on PARPORT diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 0f06fc0813c616..a470ec5689c7db 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o obj-$(CONFIG_SPI_IMX) += spi-imx.o obj-$(CONFIG_SPI_LANTIQ_SSC) += spi-lantiq-ssc.o obj-$(CONFIG_SPI_JCORE) += spi-jcore.o +obj-$(CONFIG_SPI_LITESPI) += spi-litespi.o obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o diff --git a/drivers/spi/spi-litespi.c b/drivers/spi/spi-litespi.c new file mode 100644 index 00000000000000..3ddbcfde634d36 --- /dev/null +++ b/drivers/spi/spi-litespi.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LiteSPI controller (LiteX) Driver + * + * Copyright (C) 2019 Antmicro Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "litespi" + +/* register sizes */ +#define LITESPI_SZ_CTRL 2 +#define LITESPI_SZ_STAT 1 +#define LITESPI_SZ_MOSI LITEX_SUBREG_SIZE +#define LITESPI_SZ_MISO LITEX_SUBREG_SIZE +#define LITESPI_SZ_CS 1 + +/* register offsets */ +#define LITESPI_OFF_CTRL 0x00 +#define LITESPI_OFF_STAT \ + _next_reg_off(LITESPI_OFF_CTRL, LITESPI_SZ_CTRL) +#define LITESPI_OFF_MOSI \ + _next_reg_off(LITESPI_OFF_STAT, LITESPI_SZ_STAT) +#define LITESPI_OFF_MISO \ + _next_reg_off(LITESPI_OFF_MOSI, LITESPI_SZ_MOSI) +#define LITESPI_OFF_CS \ + _next_reg_off(LITESPI_OFF_MISO, LITESPI_SZ_MISO) + +#define LITESPI_CTRL_SHIFT_BPW 8 +#define LITESPI_CTRL_START_BIT 0 + +struct litespi_hw { + struct spi_master *master; + void __iomem *base; +}; + +static inline void litespi_wait_xfer_end(struct litespi_hw *hw) +{ + while (!litex_read8(hw->base + LITESPI_OFF_STAT)) + cpu_relax(); +} + +static int litespi_rxtx(struct spi_master *master, struct spi_device *spi, + struct spi_transfer *t) +{ + struct litespi_hw *hw = spi_master_get_devdata(master); + u16 ctl_word = t->bits_per_word << LITESPI_CTRL_SHIFT_BPW; + int i; + + /* set chip select */ + litex_write8(hw->base + LITESPI_OFF_CS, BIT(spi->chip_select)); + + /* set word size */ + litex_write16(hw->base + LITESPI_OFF_CTRL, ctl_word); + + /* add start bit to ctl_word */ + ctl_word |= BIT(LITESPI_CTRL_START_BIT); + + /* + * Validated SPI transfer length is multiple of SPI word size, which + * is itself a power-of-two multiple, and fits within LITEX_SUBREG_SIZE + */ + if (t->bits_per_word <= 8) { + const u8 *tx = t->tx_buf; + u8 *rx = t->rx_buf; + + /* word size is 1 byte */ + for (i = 0; i < t->len; i++) { + if (tx) + litex_write8(hw->base + + LITESPI_OFF_MOSI, *tx++); + + litex_write16(hw->base + LITESPI_OFF_CTRL, ctl_word); + litespi_wait_xfer_end(hw); + + if (rx) + *rx++ = litex_read8(hw->base + + LITESPI_OFF_MISO); + } + } else if (t->bits_per_word <= 16) { + const u16 *tx = t->tx_buf; + u16 *rx = t->rx_buf; + + /* word size is 2 bytes */ + for (i = 0; i < t->len / 2; i++) { + if (tx) + litex_write16(hw->base + LITESPI_OFF_MOSI, + be16_to_cpu(*tx++)); + + litex_write16(hw->base + LITESPI_OFF_CTRL, ctl_word); + litespi_wait_xfer_end(hw); + + if (rx) + *rx++ = cpu_to_be16(litex_read16( + hw->base + LITESPI_OFF_MISO)); + } + } else { + const u32 *tx = t->tx_buf; + u32 *rx = t->rx_buf; + + /* word size is 4 bytes */ + for (i = 0; i < t->len / 4; i++) { + if (tx) + litex_write32(hw->base + LITESPI_OFF_MOSI, + be32_to_cpu(*tx++)); + + litex_write16(hw->base + LITESPI_OFF_CTRL, ctl_word); + litespi_wait_xfer_end(hw); + + if (rx) + *rx++ = cpu_to_be32(litex_read32( + hw->base + LITESPI_OFF_MISO)); + } + } + + return 0; +} + +static int litespi_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litespi_hw *hw; + struct spi_master *master; + struct resource *res; + int ret; + u32 val; + + master = spi_alloc_master(&pdev->dev, sizeof(*hw)); + if (!master) + return -ENOMEM; + + master->dev.of_node = pdev->dev.of_node; + master->bus_num = pdev->id; + master->transfer_one = litespi_rxtx; + master->mode_bits = SPI_MODE_0 | SPI_CS_HIGH; + master->flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX; + + /* get bits per word property */ + ret = of_property_read_u32(node, "litespi,max-bpw", &val); + if (ret) + goto err; + if (val > LITEX_SUBREG_SIZE * 8) { + ret = -EINVAL; + goto err; + } + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, val); + + /* get sck frequency */ + ret = of_property_read_u32(node, "litespi,sck-frequency", &val); + if (ret) + goto err; + master->max_speed_hz = val; + + /* get num cs */ + ret = of_property_read_u32(node, "litespi,num-cs", &val); + if (ret) + goto err; + master->num_chipselect = val; + + hw = spi_master_get_devdata(master); + hw->master = master; + + /* get base address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hw->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hw->base)) { + ret = PTR_ERR(hw->base); + goto err; + } + + /* register controller */ + ret = devm_spi_register_master(&pdev->dev, master); + if (ret) + goto err; + + return 0; + +err: + spi_master_put(master); + return ret; +} + +static const struct of_device_id litespi_match[] = { + { .compatible = "litex,litespi" }, + {} +}; +MODULE_DEVICE_TABLE(of, litespi_match); + +static struct platform_driver litespi_driver = { + .probe = litespi_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(litespi_match) + } +}; +module_platform_driver(litespi_driver) + +MODULE_AUTHOR("Antmicro Ltd "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); From a8e755fbbcf29df7238484cf08279461d445390e Mon Sep 17 00:00:00 2001 From: Kamil Rakoczy Date: Tue, 22 Dec 2020 11:37:44 -0500 Subject: [PATCH 03/31] LiteX: driver for LiteSDCard (mmc) Original author: Kamil Rakoczy Updated for DMA transfers: Maciej Dudek Clenaup, 32bit CSR, eject support: Gabriel Somlo Signed-off-by: Kamil Rakoczy Signed-off-by: Maciej Dudek Signed-off-by: Gabriel Somlo --- drivers/mmc/host/Kconfig | 6 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/litex_mmc.c | 514 +++++++++++++++++++++++++++++++++++ drivers/mmc/host/litex_mmc.h | 104 +++++++ 4 files changed, 625 insertions(+) create mode 100644 drivers/mmc/host/litex_mmc.c create mode 100644 drivers/mmc/host/litex_mmc.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index b236dfe2e87983..3e034316e61c85 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -1089,3 +1089,9 @@ config MMC_OWL config MMC_SDHCI_EXTERNAL_DMA bool + +config MMC_LITEX + tristate "Support for the MMC Controller in LiteX SOCs" + depends on OF && LITEX + help + Generic MCC driver for LiteX diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 6df5c4774260c4..2532f236c14dfe 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_MMC_CQHCI) += cqhci.o cqhci-y += cqhci-core.o cqhci-$(CONFIG_MMC_CRYPTO) += cqhci-crypto.o obj-$(CONFIG_MMC_HSQ) += mmc_hsq.o +obj-$(CONFIG_MMC_LITEX) += litex_mmc.o ifeq ($(CONFIG_CB710_DEBUG),y) CFLAGS-cb710-mmc += -DDEBUG diff --git a/drivers/mmc/host/litex_mmc.c b/drivers/mmc/host/litex_mmc.c new file mode 100644 index 00000000000000..f6e0a48656295c --- /dev/null +++ b/drivers/mmc/host/litex_mmc.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2020 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "litex_mmc.h" + +#define SDCARD_CTRL_DATA_TRANSFER_NONE 0 +#define SDCARD_CTRL_DATA_TRANSFER_READ 1 +#define SDCARD_CTRL_DATA_TRANSFER_WRITE 2 + +#define SDCARD_CTRL_RESPONSE_NONE 0 +#define SDCARD_CTRL_RESPONSE_SHORT 1 +#define SDCARD_CTRL_RESPONSE_LONG 2 + +#define SD_OK 0 +#define SD_CRCERROR 1 +#define SD_TIMEOUT 2 +#define SD_WRITEERROR 3 + +#define MAX_NR_SEGS 1 +#define MAX_NR_BLOCKS 1 +#define DATA_BLOCK_SIZE 512 + +struct litex_mmc_host { + struct mmc_host *mmc; + struct platform_device *dev; + + void __iomem *sdphy; + void __iomem *sdcore; + void __iomem *sdreader; + void __iomem *sdwriter; + + u32 resp[4]; + u16 rca; + + void *buffer; + size_t buffer_size; + dma_addr_t dma_handle; + + unsigned int freq; + unsigned int clock; + unsigned char bus_width; + bool is_bus_width_set; + bool app_cmd; +}; + + +void sdclk_set_clk(struct litex_mmc_host *host, unsigned int clk_freq) { + u32 div = clk_freq ? host->freq / clk_freq : 256; + div = roundup_pow_of_two(div); + div = min(max(div, (u32)2), (u32)256); + dev_info(&host->dev->dev, + "Requested clk_freq=%d: set to %d via div=%d\n", + clk_freq, host->freq / div, div); + litex_write16(host->sdphy + LITEX_MMC_SDPHY_CLOCKERDIV_OFF, div); +} + + +static int sdcard_wait_done(void __iomem *reg) { + u8 evt; + for (;;) { + evt = litex_read8(reg); + if (evt & 0x1) + break; + udelay(5); + } + if (evt & 0x4) + return SD_TIMEOUT; + if (evt & 0x8) + return SD_CRCERROR; + return SD_OK; +} + +static int send_cmd(struct litex_mmc_host *host, u8 cmd, u32 arg, + u8 response_len, u8 transfer) { + void __iomem *reg; + ulong n; + u8 i; + int status; + + litex_write32(host->sdcore + LITEX_MMC_SDCORE_CMDARG_OFF, arg); + litex_write32(host->sdcore + LITEX_MMC_SDCORE_CMDCMD_OFF, + cmd << 8 | transfer << 5 | response_len); + litex_write8(host->sdcore + LITEX_MMC_SDCORE_CMDSND_OFF, 1); + + status = sdcard_wait_done(host->sdcore + LITEX_MMC_SDCORE_CMDEVT_OFF); + if (status != SD_OK) { + pr_err("Command (cmd %d) failed, status %d\n", cmd, status); + return status; + } + + if (response_len != SDCARD_CTRL_RESPONSE_NONE) { + reg = host->sdcore + LITEX_MMC_SDCORE_CMDRSP_OFF; + for (i = 0; i < 4; i++) { + host->resp[i] = litex_read32(reg); + reg += _next_reg_off(0, sizeof(u32)); + } + } + + if (!host->app_cmd && cmd == SD_SEND_RELATIVE_ADDR) { + host->rca = (host->resp[3] >> 16) & 0xffff; + } + + host->app_cmd = (cmd == MMC_APP_CMD); + + if (transfer == SDCARD_CTRL_DATA_TRANSFER_NONE) + return status; /* SD_OK from prior sdcard_wait_done(cmd_evt) */ + + status = sdcard_wait_done(host->sdcore + LITEX_MMC_SDCORE_DATAEVT_OFF); + if (status != SD_OK){ + pr_err("Data xfer (cmd %d) failed, status %d\n", cmd, status); + return status; + } + + /* wait for completion of (read or write) DMA transfer */ + reg = (transfer == SDCARD_CTRL_DATA_TRANSFER_READ) ? + host->sdreader + LITEX_MMC_SDBLK2MEM_DONE_OFF : + host->sdwriter + LITEX_MMC_SDMEM2BLK_DONE_OFF; + n = jiffies + (HZ << 1); + while ((litex_read8(reg) & 0x01) == 0) + if (time_after(jiffies, n)) { + pr_err("DMA timeout (cmd %d)\n", cmd); + return SD_TIMEOUT; + } + + return status; +} + +// CMD55 +static inline int send_app_cmd(struct litex_mmc_host *host) { + return send_cmd(host, MMC_APP_CMD, host->rca << 16, + SDCARD_CTRL_RESPONSE_SHORT, + SDCARD_CTRL_DATA_TRANSFER_NONE); +} + +// ACMD6 +static inline int send_app_set_bus_width_cmd( + struct litex_mmc_host *host, u32 width) { + return send_cmd(host, SD_APP_SET_BUS_WIDTH, width, + SDCARD_CTRL_RESPONSE_SHORT, + SDCARD_CTRL_DATA_TRANSFER_NONE); +} + +static int litex_set_bus_width(struct litex_mmc_host *host) { + bool app_cmd_sent = host->app_cmd; /* was preceding command app_cmd? */ + int status; + + /* ensure 'app_cmd' precedes 'app_set_bus_width_cmd' */ + if (!app_cmd_sent) + send_app_cmd(host); + + status = send_app_set_bus_width_cmd(host, host->bus_width); + + /* re-send 'app_cmd' if necessary */ + if (app_cmd_sent) + send_app_cmd(host); + + return status; +} + +static int litex_get_cd(struct mmc_host *mmc) +{ + struct litex_mmc_host *host = mmc_priv(mmc); + int gpio_cd = mmc_gpio_get_cd(mmc); + int ret; + + if (!mmc_card_is_removable(mmc)) + return 1; + + if (gpio_cd >= 0) + /* GPIO based card-detect explicitly specified in DTS */ + ret = !!gpio_cd; + else + /* use gateware card-detect bit by default */ + ret = !litex_read8(host->sdphy + + LITEX_MMC_SDPHY_CARDDETECT_OFF); + + /* ensure bus width will be set (again) upon card (re)insertion */ + if (ret == 0) + host->is_bus_width_set = false; + + return ret; +} + +/* + * Send request to a card. Command, data transfer, things like this. + * Call mmc_request_done() when finished. + */ +static void litex_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct litex_mmc_host *host = mmc_priv(mmc); + struct platform_device *pdev = to_platform_device(mmc->parent); + struct device *dev = &pdev->dev; + struct mmc_data *data = mrq->data; + struct mmc_command *cmd = mrq->cmd; + unsigned int retries = cmd->retries; + int status; + + u32 response_len = SDCARD_CTRL_RESPONSE_NONE; + u32 transfer = SDCARD_CTRL_DATA_TRANSFER_NONE; + + if (cmd->flags & MMC_RSP_136) { + response_len = SDCARD_CTRL_RESPONSE_LONG; + } else if (cmd->flags & MMC_RSP_PRESENT) { + response_len = SDCARD_CTRL_RESPONSE_SHORT; + } + + if (data) { + // This is a good time (i.e. last moment) to finally set bus + // width. Ofc if not set yet. + if (!host->is_bus_width_set) { + ulong n = jiffies + 2 * HZ; // 500ms timeout + while (litex_set_bus_width(host) != SD_OK) { + if (time_after(jiffies, n)) { + dev_warn(dev, "Can't set bus width!\n"); + cmd->error = -ETIMEDOUT; + mmc_request_done(mmc, mrq); + return; + } + } + host->is_bus_width_set = true; + } + + if (mrq->data->flags & MMC_DATA_READ) { + litex_write8(host->sdreader + + LITEX_MMC_SDBLK2MEM_ENA_OFF, 0); + litex_write64(host->sdreader + + LITEX_MMC_SDBLK2MEM_BASE_OFF, + host->dma_handle); + litex_write32(host->sdreader + + LITEX_MMC_SDBLK2MEM_LEN_OFF, + data->blksz * data->blocks); + litex_write8(host->sdreader + + LITEX_MMC_SDBLK2MEM_ENA_OFF, 1); + + transfer = SDCARD_CTRL_DATA_TRANSFER_READ; + + } else if (mrq->data->flags & MMC_DATA_WRITE) { + int write_length = min(data->blksz * data->blocks, + (u32)host->buffer_size); + + sg_copy_to_buffer(data->sg, data->sg_len, + host->buffer, write_length); + + litex_write8(host->sdwriter + + LITEX_MMC_SDMEM2BLK_ENA_OFF, 0); + litex_write64(host->sdwriter + + LITEX_MMC_SDMEM2BLK_BASE_OFF, + host->dma_handle); + litex_write32(host->sdwriter + + LITEX_MMC_SDMEM2BLK_LEN_OFF, + write_length); + litex_write8(host->sdwriter + + LITEX_MMC_SDMEM2BLK_ENA_OFF, 1); + + transfer = SDCARD_CTRL_DATA_TRANSFER_WRITE; + } else { + dev_warn(dev, "Data present w/o read or write flag.\n"); + // Intentionally continue: set cmd status, mark req done + } + + litex_write16(host->sdcore + LITEX_MMC_SDCORE_BLKLEN_OFF, + data->blksz); + litex_write32(host->sdcore + LITEX_MMC_SDCORE_BLKCNT_OFF, + data->blocks); + } + + do { + status = send_cmd(host, cmd->opcode, cmd->arg, + response_len, transfer); + } while (status != SD_OK && retries-- > 0); + + switch (status) { + case SD_OK: + cmd->error = 0; + break; + case SD_TIMEOUT: + cmd->error = -ETIMEDOUT; + break; + case SD_CRCERROR: + cmd->error = -EILSEQ; + break; + case SD_WRITEERROR: + cmd->error = -EINVAL; + break; + default: + cmd->error = -EINVAL; + break; + } + + // It looks strange I know, but it's as it should be + if (response_len == SDCARD_CTRL_RESPONSE_SHORT) { + cmd->resp[0] = host->resp[3]; + cmd->resp[1] = host->resp[2] & 0xFF; + } else if (response_len == SDCARD_CTRL_RESPONSE_LONG) { + cmd->resp[0] = host->resp[0]; + cmd->resp[1] = host->resp[1]; + cmd->resp[2] = host->resp[2]; + cmd->resp[3] = host->resp[3]; + } + + if (status == SD_OK && transfer != SDCARD_CTRL_DATA_TRANSFER_NONE) { + data->bytes_xfered = min(data->blksz * data->blocks, + (u32)(MAX_NR_BLOCKS*DATA_BLOCK_SIZE)); + if (transfer == SDCARD_CTRL_DATA_TRANSFER_READ) { + sg_copy_from_buffer(data->sg, sg_nents(data->sg), + host->buffer, data->bytes_xfered); + } + } + + mmc_request_done(mmc, mrq); +} + +static void litex_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct litex_mmc_host *host = mmc_priv(mmc); + + if (ios->clock != host->clock) { + sdclk_set_clk(host, ios->clock); + host->clock = ios->clock; + } + + // Technically there should be some code forsetting requested bus width. + // We can't send "send_bus_width" command to the card at this moment + // because the card is not in the right state (the command is only accepted + // in "trans" state). Requested width should be stored and send when it is + // possible (see litex_request), but litesdcard supports only 4 bit bus + // width, so it is known what must be set. +} + +static const struct mmc_host_ops litex_mmc_ops = { + .get_cd = litex_get_cd, + .request = litex_request, + .set_ios = litex_set_ios, +}; + +#define MAP_RESOURCE(res_name, idx) \ +{ \ + res = platform_get_resource(pdev, IORESOURCE_MEM, idx); \ + host->res_name = devm_ioremap_resource(&pdev->dev, res); \ + if (IS_ERR(host->res_name)) { \ + ret = PTR_ERR(host->res_name); \ + goto err_exit; \ + } \ +} + +static int litex_mmc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct litex_mmc_host *host; + struct device_node *node, *cpu; + struct mmc_host *mmc; + int ret; + + node = pdev->dev.of_node; + if (!node) + return -ENODEV; + + host = devm_kzalloc(&pdev->dev, sizeof(struct litex_mmc_host), + GFP_KERNEL); + if (!host) + return -ENOMEM; + + mmc = mmc_alloc_host(sizeof(struct litex_mmc_host), &pdev->dev); + if (!mmc) + return -ENOMEM; + + host = mmc_priv(mmc); + host->mmc = mmc; + host->dev = pdev; + // Initial state + host->clock = 0; + + cpu = of_find_node_by_name(NULL, "cpu"); + ret = of_property_read_u32(cpu, "clock-frequency", &host->freq); + of_node_put(cpu); + if (ret) + goto err_exit; + + // litesdcard only supports 4-bit bus width + host->bus_width = MMC_BUS_WIDTH_4; + host->is_bus_width_set = false; + host->app_cmd = false; + + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))){ + pr_err("Unable to set DMA driver failed\n"); + goto err_exit; + } + + host->buffer_size = MAX_NR_BLOCKS * DATA_BLOCK_SIZE * 2; + host->buffer = dma_alloc_coherent(&pdev->dev, host->buffer_size, + &host->dma_handle, GFP_DMA); + if (host->buffer == NULL) + goto err_exit; + + MAP_RESOURCE(sdphy, 0); + MAP_RESOURCE(sdcore, 1); + MAP_RESOURCE(sdreader, 2); + MAP_RESOURCE(sdwriter, 3); + + ret = mmc_of_parse(mmc); + if (ret) + goto err_exit; + + mmc->caps = MMC_CAP_NEEDS_POLL | /* FIXME: we need this if no IRQ! */ + MMC_CAP_WAIT_WHILE_BUSY | + MMC_CAP_DRIVER_TYPE_D; + mmc->caps2 = MMC_CAP2_NO_SDIO | + MMC_CAP2_FULL_PWR_CYCLE | + MMC_CAP2_NO_WRITE_PROTECT; + mmc->ops = &litex_mmc_ops; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + + mmc->max_blk_size = DATA_BLOCK_SIZE; + mmc->max_blk_count = MAX_NR_BLOCKS; + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; + mmc->max_segs = MAX_NR_SEGS; + mmc->max_seg_size = 512; + + mmc->f_min = 125 * 1e5; // sys_clk/256 is minimal frequency mmcm can produce, set minimal to 12.5Mhz on lower frequencies, sdcard sometimes do not initialize properly + mmc->f_max = 50 * 1e6; // 50Mhz is max frequency sd card can support + + platform_set_drvdata(pdev, host); + + ret = mmc_add_host(mmc); + if (ret < 0) { + goto err_exit; + } + + /* ensure DMA bus masters are disabled */ + litex_write8(host->sdreader + LITEX_MMC_SDBLK2MEM_ENA_OFF, 0); + litex_write8(host->sdwriter + LITEX_MMC_SDMEM2BLK_ENA_OFF, 0); + + return 0; + +err_exit: + kfree(host->buffer); + mmc_free_host(mmc); + return ret; +} + +static int litex_mmc_remove(struct platform_device *pdev) +{ + struct litex_mmc_host *host = dev_get_drvdata(&pdev->dev); + + mmc_remove_host(host->mmc); + mmc_free_host(host->mmc); + + return 0; +} + +static const struct of_device_id litex_match[] = { + { .compatible = "litex,mmc" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_match); + +static struct platform_driver litex_mmc_driver = { + .driver = { + .name = "litex-mmc", + .of_match_table = of_match_ptr(litex_match), + }, + .probe = litex_mmc_probe, + .remove = litex_mmc_remove, +}; + +module_platform_driver(litex_mmc_driver); + +MODULE_DESCRIPTION("LiteX SDCard driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/mmc/host/litex_mmc.h b/drivers/mmc/host/litex_mmc.h new file mode 100644 index 00000000000000..dd7807529ee71d --- /dev/null +++ b/drivers/mmc/host/litex_mmc.h @@ -0,0 +1,104 @@ +#ifndef _LITEX_MMC_H_ +#define _LITEX_MMC_H_ + +#include + +/* register sizes (in bytes) */ +#define LITEX_MMC_SDPHY_CARDDETECT_SIZE 1 +#define LITEX_MMC_SDPHY_CLOCKERDIV_SIZE 2 +#define LITEX_MMC_SDPHY_INITIALIZE_SIZE 1 +#define LITEX_MMC_SDPHY_WRITESTATUS_SIZE 1 + +#define LITEX_MMC_SDCORE_CMDARG_SIZE 4 +#define LITEX_MMC_SDCORE_CMDCMD_SIZE 4 +#define LITEX_MMC_SDCORE_CMDSND_SIZE 1 +#define LITEX_MMC_SDCORE_CMDRSP_SIZE 16 +#define LITEX_MMC_SDCORE_CMDEVT_SIZE 1 +#define LITEX_MMC_SDCORE_DATAEVT_SIZE 1 +#define LITEX_MMC_SDCORE_BLKLEN_SIZE 2 +#define LITEX_MMC_SDCORE_BLKCNT_SIZE 4 + +/* sdreader */ +#define LITEX_MMC_SDBLK2MEM_BASE_SIZE 8 +#define LITEX_MMC_SDBLK2MEM_LEN_SIZE 4 +#define LITEX_MMC_SDBLK2MEM_ENA_SIZE 1 +#define LITEX_MMC_SDBLK2MEM_DONE_SIZE 1 +#define LITEX_MMC_SDBLK2MEM_LOOP_SIZE 1 + +/* sdwriter */ +#define LITEX_MMC_SDMEM2BLK_BASE_SIZE 8 +#define LITEX_MMC_SDMEM2BLK_LEN_SIZE 4 +#define LITEX_MMC_SDMEM2BLK_ENA_SIZE 1 +#define LITEX_MMC_SDMEM2BLK_DONE_SIZE 1 +#define LITEX_MMC_SDMEM2BLK_LOOP_SIZE 1 +#define LITEX_MMC_SDMEM2BLK_OFF_SIZE 4 + +/* register offsets (w.r.t. region base addr.) */ +#define LITEX_MMC_SDPHY_CARDDETECT_OFF 0x00 +#define LITEX_MMC_SDPHY_CLOCKERDIV_OFF \ + _next_reg_off(LITEX_MMC_SDPHY_CARDDETECT_OFF, \ + LITEX_MMC_SDPHY_CARDDETECT_SIZE) +#define LITEX_MMC_SDPHY_INITIALIZE_OFF \ + _next_reg_off(LITEX_MMC_SDPHY_CLOCKERDIV_OFF, \ + LITEX_MMC_SDPHY_CLOCKERDIV_SIZE) +#define LITEX_MMC_SDPHY_WRITESTATUS_OFF \ + _next_reg_off(LITEX_MMC_SDPHY_INITIALIZE_OFF, \ + LITEX_MMC_SDPHY_INITIALIZE_SIZE) + +#define LITEX_MMC_SDCORE_CMDARG_OFF 0x00 +#define LITEX_MMC_SDCORE_CMDCMD_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDARG_OFF, \ + LITEX_MMC_SDCORE_CMDARG_SIZE) +#define LITEX_MMC_SDCORE_CMDSND_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDCMD_OFF, \ + LITEX_MMC_SDCORE_CMDCMD_SIZE) +#define LITEX_MMC_SDCORE_CMDRSP_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDSND_OFF, \ + LITEX_MMC_SDCORE_CMDSND_SIZE) +#define LITEX_MMC_SDCORE_CMDEVT_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDRSP_OFF, \ + LITEX_MMC_SDCORE_CMDRSP_SIZE) +#define LITEX_MMC_SDCORE_DATAEVT_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDEVT_OFF, \ + LITEX_MMC_SDCORE_CMDEVT_SIZE) +#define LITEX_MMC_SDCORE_BLKLEN_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_DATAEVT_OFF, \ + LITEX_MMC_SDCORE_DATAEVT_SIZE) +#define LITEX_MMC_SDCORE_BLKCNT_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_BLKLEN_OFF, \ + LITEX_MMC_SDCORE_BLKLEN_SIZE) + +/* sdreader */ +#define LITEX_MMC_SDBLK2MEM_BASE_OFF 0x00 +#define LITEX_MMC_SDBLK2MEM_LEN_OFF \ + _next_reg_off(LITEX_MMC_SDBLK2MEM_BASE_OFF, \ + LITEX_MMC_SDBLK2MEM_BASE_SIZE) +#define LITEX_MMC_SDBLK2MEM_ENA_OFF \ + _next_reg_off(LITEX_MMC_SDBLK2MEM_LEN_OFF, \ + LITEX_MMC_SDBLK2MEM_LEN_SIZE) +#define LITEX_MMC_SDBLK2MEM_DONE_OFF \ + _next_reg_off(LITEX_MMC_SDBLK2MEM_ENA_OFF, \ + LITEX_MMC_SDBLK2MEM_ENA_SIZE) +#define LITEX_MMC_SDBLK2MEM_LOOP_OFF \ + _next_reg_off(LITEX_MMC_SDBLK2MEM_DONE_OFF, \ + LITEX_MMC_SDBLK2MEM_DONE_SIZE) + +/* sdwriter */ +#define LITEX_MMC_SDMEM2BLK_BASE_OFF 0x00 +#define LITEX_MMC_SDMEM2BLK_LEN_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_BASE_OFF, \ + LITEX_MMC_SDMEM2BLK_BASE_SIZE) +#define LITEX_MMC_SDMEM2BLK_ENA_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_LEN_OFF, \ + LITEX_MMC_SDMEM2BLK_LEN_SIZE) +#define LITEX_MMC_SDMEM2BLK_DONE_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_ENA_OFF, \ + LITEX_MMC_SDMEM2BLK_ENA_SIZE) +#define LITEX_MMC_SDMEM2BLK_LOOP_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_DONE_OFF, \ + LITEX_MMC_SDMEM2BLK_DONE_SIZE) +#define LITEX_MMC_SDMEM2BLK_OFF_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_LOOP_OFF, \ + LITEX_MMC_SDMEM2BLK_LOOP_SIZE) + +#endif /* _LITEX_MMC_H_ */ From e8c460f13076bb570b604f1a9e9b9c5d0db80d38 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Tue, 22 Dec 2020 16:19:42 -0500 Subject: [PATCH 04/31] LiteX: add defconfig files for linux-on-litex-rocket design Add defconfig for LiteX SoC with RocketChip CPU, LiteETH, LiteSDCard, and optional support for spi-mode SDCard. Signed-off-by: Gabriel Somlo --- arch/riscv/configs/litex_rocket_defconfig | 113 ++++++++++++++++++ .../configs/litex_rocket_initramfs.config | 1 + 2 files changed, 114 insertions(+) create mode 100644 arch/riscv/configs/litex_rocket_defconfig create mode 100644 arch/riscv/configs/litex_rocket_initramfs.config diff --git a/arch/riscv/configs/litex_rocket_defconfig b/arch/riscv/configs/litex_rocket_defconfig new file mode 100644 index 00000000000000..0a26721a9073a8 --- /dev/null +++ b/arch/riscv/configs/litex_rocket_defconfig @@ -0,0 +1,113 @@ +CONFIG_DEFAULT_HOSTNAME="litex" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_SGETMASK_SYSCALL=y +CONFIG_BPF_SYSCALL=y +CONFIG_EMBEDDED=y +CONFIG_HZ_100=y +CONFIG_PARTITION_ADVANCED=y +# CONFIG_EFI_PARTITION is not set +# CONFIG_MQ_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_KYBER is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +# CONFIG_INET_DIAG is not set +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_NBD=y +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +CONFIG_LITEX_LITEETH=y +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_WLAN is not set +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_CONSOLE_TRANSLATIONS is not set +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_SERIAL_EARLYCON_RISCV_SBI=y +CONFIG_HVC_RISCV_SBI=y +# CONFIG_HW_RANDOM is not set +CONFIG_SPI=y +CONFIG_SPI_LITESPI=y +# CONFIG_HWMON is not set +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE_COLUMNS=128 +CONFIG_DUMMY_CONSOLE_ROWS=32 +# CONFIG_HID is not set +# CONFIG_USB_SUPPORT is not set +CONFIG_MMC=y +# CONFIG_PWRSEQ_EMMC is not set +# CONFIG_PWRSEQ_SIMPLE is not set +CONFIG_MMC_SPI=y +CONFIG_MMC_LITEX=y +CONFIG_LITEX_SOC_CONTROLLER=y +CONFIG_SIFIVE_PLIC=y +CONFIG_RAS=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_F2FS_FS=y +# CONFIG_F2FS_FS_POSIX_ACL is not set +# CONFIG_DNOTIFY is not set +CONFIG_FANOTIFY=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_UTF8=y +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_TMPFS=y +# CONFIG_MISC_FILESYSTEMS is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_UTF8=y +CONFIG_SECURITY=y +CONFIG_CRYPTO_RSA=y +# CONFIG_CRYPTO_HW is not set +CONFIG_PRINTK_TIME=y +CONFIG_CONSOLE_LOGLEVEL_DEFAULT=15 +CONFIG_MESSAGE_LOGLEVEL_DEFAULT=7 +CONFIG_DEBUG_INFO=y diff --git a/arch/riscv/configs/litex_rocket_initramfs.config b/arch/riscv/configs/litex_rocket_initramfs.config new file mode 100644 index 00000000000000..658bbdb98e9d27 --- /dev/null +++ b/arch/riscv/configs/litex_rocket_initramfs.config @@ -0,0 +1 @@ +CONFIG_INITRAMFS_SOURCE="initramfs.cpio" From 15ca79a8fb5a5619da2bb328a4a509635d21abe1 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 22 Dec 2020 16:26:17 -0500 Subject: [PATCH 05/31] LiteX: driver for LiteGPIO interface Signed-off-by: Robert Winkler FIXME: not updated or tested for 32-bit CSR data width, 64-bit CPU (gls) --- .../devicetree/bindings/gpio/litex,gpio.txt | 23 ++ drivers/gpio/Kconfig | 6 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-litex.c | 218 ++++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/litex,gpio.txt create mode 100644 drivers/gpio/gpio-litex.c diff --git a/Documentation/devicetree/bindings/gpio/litex,gpio.txt b/Documentation/devicetree/bindings/gpio/litex,gpio.txt new file mode 100644 index 00000000000000..ab9aef6c6eb7fb --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/litex,gpio.txt @@ -0,0 +1,23 @@ +LiteX GPIO controller + +Required properties: +- compatible: should be "litex,gpio" +- reg: base address and length of the register +- litex,ngpio: number of gpio pins in port +- litex,direction: direction of gpio port, should be "in" or "out" + +Examples: + +gpio@f0003000 { + compatible = "litex,gpio"; + reg = <0x0 0xf0003800 0x0 0x1>; + litex,ngpio = <4>; + litex,direction = "in"; +}; + +gpio@f0003800 { + compatible = "litex,gpio"; + reg = <0x0 0xf0003800 0x0 0x1>; + litex,ngpio = <4>; + litex,direction = "out"; +}; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e3607ec4c2e837..497bfd5e4d62ad 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -361,6 +361,12 @@ config GPIO_LOGICVC Say yes here to support GPIO functionality of the Xylon LogiCVC programmable logic block. +config GPIO_LITEX + tristate "LiteX GPIO support" + depends on OF && HAS_IOMEM && LITEX_SOC_CONTROLLER + help + driver for GPIO functionality on LiteX + config GPIO_LOONGSON bool "Loongson-2/3 GPIO support" depends on CPU_LOONGSON2EF || CPU_LOONGSON64 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index c58a90a3c3b116..0b1a8a23c3399a 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_GPIO_IT87) += gpio-it87.o obj-$(CONFIG_GPIO_IXP4XX) += gpio-ixp4xx.o obj-$(CONFIG_GPIO_JANZ_TTL) += gpio-janz-ttl.o obj-$(CONFIG_GPIO_KEMPLD) += gpio-kempld.o +obj-$(CONFIG_GPIO_LITEX) += gpio-litex.o obj-$(CONFIG_GPIO_LOGICVC) += gpio-logicvc.o obj-$(CONFIG_GPIO_LOONGSON1) += gpio-loongson1.o obj-$(CONFIG_GPIO_LOONGSON) += gpio-loongson.o diff --git a/drivers/gpio/gpio-litex.c b/drivers/gpio/gpio-litex.c new file mode 100644 index 00000000000000..127c5b97f55e28 --- /dev/null +++ b/drivers/gpio/gpio-litex.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_PINS_MAX 32 + +struct litex_gpio { + void __iomem *membase; + int port_direction; + int reg_span; + struct gpio_chip chip; +}; + +/* API functions */ + +static int litex_gpio_get_value(struct gpio_chip *chip, unsigned int offset) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + u32 regv; + + if (offset >= chip->ngpio) + return -EINVAL; + + regv = litex_get_reg(gpio_s->membase, gpio_s->reg_span); + return !!(regv & BIT(offset)); +} + +static int litex_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + u32 regv; + + if (*mask >= (1 << chip->ngpio)) + return -EINVAL; + + regv = litex_get_reg(gpio_s->membase, gpio_s->reg_span); + *bits = (regv & *mask); + return 0; +} + +static void litex_gpio_set_value(struct gpio_chip *chip, unsigned int offset, + int val) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + u32 regv, new_regv; + + if (offset >= chip->ngpio) + return; + + regv = litex_get_reg(gpio_s->membase, gpio_s->reg_span); + new_regv = (regv & ~BIT(offset)) | (!!val << offset); + litex_set_reg(gpio_s->membase, gpio_s->reg_span, new_regv); +} + +static void litex_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + u32 regv, new_regv; + + if (*mask >= (1 << chip->ngpio)) + return; + + regv = litex_get_reg(gpio_s->membase, gpio_s->reg_span); + new_regv = (regv & ~(*mask)) | (*bits); + litex_set_reg(gpio_s->membase, gpio_s->reg_span, new_regv); +} + +static int litex_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + + return gpio_s->port_direction; +} + +static int litex_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + + if (gpio_s->port_direction != GPIOF_DIR_IN) + return -ENOTSUPP; + else + return 0; +} + +static int litex_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + + if (gpio_s->port_direction != GPIOF_DIR_OUT) + return -ENOTSUPP; + else + return 0; +} + +/* Driver functions */ + +static int litex_gpio_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litex_gpio *gpio_s; + struct resource *res; + int ret_i; + + int dt_ngpio; + const char *dt_direction; + + if (!node) + return -ENODEV; + + gpio_s = devm_kzalloc(&pdev->dev, sizeof(*gpio_s), GFP_KERNEL); + if (!gpio_s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + gpio_s->membase = devm_of_iomap(&pdev->dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(gpio_s->membase)) + return -EIO; + + ret_i = of_property_read_u32(node, "litex,ngpio", &dt_ngpio); + if (ret_i < 0) { + dev_err(&pdev->dev, "No litex,ngpio entry in the dts file\n"); + return -ENODEV; + } + if (dt_ngpio >= GPIO_PINS_MAX) { + dev_err(&pdev->dev, + "LiteX GPIO driver cannot use more than %d pins\n", + GPIO_PINS_MAX); + return -EINVAL; + } + + ret_i = of_property_read_string(node, "litex,direction", + &dt_direction); + if (ret_i < 0) { + dev_err(&pdev->dev, "No litex,direction entry in the dts file\n"); + return -ENODEV; + } + + if (!strcmp(dt_direction, "in")) + gpio_s->port_direction = GPIOF_DIR_IN; + else if (!strcmp(dt_direction, "out")) + gpio_s->port_direction = GPIOF_DIR_OUT; + else + return -ENODEV; + + /* Assign API functions */ + + gpio_s->chip.label = "litex_gpio"; + gpio_s->chip.owner = THIS_MODULE; + gpio_s->chip.get = litex_gpio_get_value; + gpio_s->chip.get_multiple = litex_gpio_get_multiple; + gpio_s->chip.set = litex_gpio_set_value; + gpio_s->chip.set_multiple = litex_gpio_set_multiple; + gpio_s->chip.get_direction = litex_gpio_get_direction; + gpio_s->chip.direction_input = litex_gpio_direction_input; + gpio_s->chip.direction_output = litex_gpio_direction_output; + gpio_s->chip.parent = &pdev->dev; + gpio_s->chip.base = -1; + gpio_s->chip.ngpio = dt_ngpio; + gpio_s->chip.can_sleep = false; + + gpio_s->reg_span = (dt_ngpio + LITEX_SUBREG_SIZE_BIT - 1) / + LITEX_SUBREG_SIZE_BIT; + + platform_set_drvdata(pdev, gpio_s); + return devm_gpiochip_add_data(&pdev->dev, &gpio_s->chip, gpio_s); +} + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,gpio"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_gpio_driver = { + .driver = { + .name = "litex-gpio", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_gpio_probe, +}; + +module_platform_driver(litex_gpio_driver); + +MODULE_DESCRIPTION("LiteX gpio driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); From f6f825b40ea791b501441557aa9ffb2a1b7e2cdb Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Sun, 27 Dec 2020 20:47:26 -0500 Subject: [PATCH 06/31] fixup over ("LiteX: driver for LiteGPIO interface") FIXME: if possible, replace calls to '_litex_[get|set]_reg()' with the appropriate 'litex_[read|write][8|16|32|64]()'. If the size of a LiteX CSR access can't be determined at compile time, we should make available a set of public 'litex_[get|set]_reg()' methods that add 'BUG_ON(reg_size > sizeof(u64) || reg_size < 1)' on top of the call to '_litex_[get|set]_reg()'. --- drivers/gpio/gpio-litex.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-litex.c b/drivers/gpio/gpio-litex.c index 127c5b97f55e28..70e05249c23bf4 100644 --- a/drivers/gpio/gpio-litex.c +++ b/drivers/gpio/gpio-litex.c @@ -45,7 +45,7 @@ static int litex_gpio_get_value(struct gpio_chip *chip, unsigned int offset) if (offset >= chip->ngpio) return -EINVAL; - regv = litex_get_reg(gpio_s->membase, gpio_s->reg_span); + regv = _litex_get_reg(gpio_s->membase, gpio_s->reg_span); return !!(regv & BIT(offset)); } @@ -58,7 +58,7 @@ static int litex_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask, if (*mask >= (1 << chip->ngpio)) return -EINVAL; - regv = litex_get_reg(gpio_s->membase, gpio_s->reg_span); + regv = _litex_get_reg(gpio_s->membase, gpio_s->reg_span); *bits = (regv & *mask); return 0; } @@ -72,9 +72,9 @@ static void litex_gpio_set_value(struct gpio_chip *chip, unsigned int offset, if (offset >= chip->ngpio) return; - regv = litex_get_reg(gpio_s->membase, gpio_s->reg_span); + regv = _litex_get_reg(gpio_s->membase, gpio_s->reg_span); new_regv = (regv & ~BIT(offset)) | (!!val << offset); - litex_set_reg(gpio_s->membase, gpio_s->reg_span, new_regv); + _litex_set_reg(gpio_s->membase, gpio_s->reg_span, new_regv); } static void litex_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask, @@ -86,9 +86,9 @@ static void litex_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask, if (*mask >= (1 << chip->ngpio)) return; - regv = litex_get_reg(gpio_s->membase, gpio_s->reg_span); + regv = _litex_get_reg(gpio_s->membase, gpio_s->reg_span); new_regv = (regv & ~(*mask)) | (*bits); - litex_set_reg(gpio_s->membase, gpio_s->reg_span, new_regv); + _litex_set_reg(gpio_s->membase, gpio_s->reg_span, new_regv); } static int litex_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) From 401938fc15623fcbc5688e1233fbc59853142640 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 22 Dec 2020 16:29:47 -0500 Subject: [PATCH 07/31] LiteX: driver for I2CMaster Signed-off-by: Robert Winkler FIXME: not updated or tested for 32-bit CSR data width, 64-bit CPU (gls) --- .../devicetree/bindings/i2c/i2c-litex.txt | 12 ++ drivers/i2c/busses/Kconfig | 7 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-litex.c | 156 ++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/i2c-litex.txt create mode 100644 drivers/i2c/busses/i2c-litex.c diff --git a/Documentation/devicetree/bindings/i2c/i2c-litex.txt b/Documentation/devicetree/bindings/i2c/i2c-litex.txt new file mode 100644 index 00000000000000..e78a87155a9544 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-litex.txt @@ -0,0 +1,12 @@ +LiteX I2C controller + +Required properties: +- compatible: should be "litex,i2c" +- reg: base address of configuration registers with length + +Examples: + +i2c@f0003000 { + compatible = "litex,i2c"; + reg = <0x0 0xf0003000 0x0 0x5>; +}; diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 05ebf7546e3f61..4bac4a3f902905 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -715,6 +715,13 @@ config I2C_KEMPLD This driver can also be built as a module. If so, the module will be called i2c-kempld. +config I2C_LITEX + tristate "LiteX I2C support" + depends on OF && HAS_IOMEM && LITEX_SOC_CONTROLLER + select I2C_ALGOBIT + help + This enables I2C bitbang driver for LiteX SoC builder. + config I2C_LPC2K tristate "I2C bus support for NXP LPC2K/LPC178x/18xx/43xx" depends on OF && (ARCH_LPC18XX || COMPILE_TEST) diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 615f35e3e31f88..158470e962e442 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_I2C_IMX_LPI2C) += i2c-imx-lpi2c.o obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o obj-$(CONFIG_I2C_JZ4780) += i2c-jz4780.o obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o +obj-$(CONFIG_I2C_LITEX) += i2c-litex.o obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o obj-$(CONFIG_I2C_MESON) += i2c-meson.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o diff --git a/drivers/i2c/busses/i2c-litex.c b/drivers/i2c/busses/i2c-litex.c new file mode 100644 index 00000000000000..1303e6b30c6d5f --- /dev/null +++ b/drivers/i2c/busses/i2c-litex.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#define REGISTER_SIZE 1 +#define OFFSET_REG_W 0x0 +#define OFFSET_REG_R 0x4 + +#define BITPOS_SCL 0 +#define BITPOS_SDA_DIR 1 +#define BITPOS_SDA_W 2 +#define BITPOS_SDA_R 0 + +#define SETDIR_SDA_OUTPUT 1 +#define SETDIR_SDA_INPUT 0 + +#define DRIVER_ALGO_BIT_UDELAY 20 + +struct litex_i2c { + void __iomem *reg_w; + void __iomem *reg_r; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo_data; +}; + +/* Helper functions */ + +static inline void litex_set_bit(void __iomem *mem, int bit, int val) +{ + u32 regv, new_regv; + + regv = litex_get_reg(mem, REGISTER_SIZE); + new_regv = (regv & ~BIT(bit)) | ((!!val) << bit); + litex_set_reg(mem, REGISTER_SIZE, new_regv); +} + +static inline int litex_get_bit(void __iomem *mem, int bit) +{ + u32 regv; + + regv = litex_get_reg(mem, REGISTER_SIZE); + return !!(regv & BIT(bit)); +} + +/* API functions */ + +static void litex_i2c_setscl(void *data, int state) +{ + struct litex_i2c *i2c = (struct litex_i2c *) data; + + litex_set_bit(i2c->reg_w, BITPOS_SCL, state); +} + +static void litex_i2c_setsda(void *data, int state) +{ + struct litex_i2c *i2c = (struct litex_i2c *) data; + + litex_set_bit(i2c->reg_w, BITPOS_SDA_DIR, SETDIR_SDA_OUTPUT); + litex_set_bit(i2c->reg_w, BITPOS_SDA_W, state); +} + +static int litex_i2c_getsda(void *data) +{ + struct litex_i2c *i2c = (struct litex_i2c *) data; + + litex_set_bit(i2c->reg_w, BITPOS_SDA_DIR, SETDIR_SDA_INPUT); + return litex_get_bit(i2c->reg_r, BITPOS_SDA_R); +} + +/* Driver functions */ + +static int litex_i2c_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + void __iomem *membase; + struct litex_i2c *i2c_s; + struct resource *res; + + if (!node) + return -ENODEV; + + i2c_s = devm_kzalloc(&pdev->dev, sizeof(*i2c_s), GFP_KERNEL); + if (!i2c_s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + membase = devm_of_iomap(&pdev->dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(membase)) + return -EIO; + + i2c_s->reg_w = membase + OFFSET_REG_W; + i2c_s->reg_r = membase + OFFSET_REG_R; + + strncpy(i2c_s->adapter.name, "litex_i2c_adapter", + sizeof(i2c_s->adapter.name)); + i2c_s->adapter.owner = THIS_MODULE; + i2c_s->adapter.algo_data = &i2c_s->algo_data; + i2c_s->adapter.dev.parent = &pdev->dev; + i2c_s->adapter.dev.of_node = node; + i2c_s->algo_data.data = i2c_s; + + i2c_s->algo_data.setsda = litex_i2c_setsda; + i2c_s->algo_data.setscl = litex_i2c_setscl; + i2c_s->algo_data.getsda = litex_i2c_getsda; + i2c_s->algo_data.getscl = NULL; + i2c_s->algo_data.udelay = DRIVER_ALGO_BIT_UDELAY; + i2c_s->algo_data.timeout = HZ; + + platform_set_drvdata(pdev, i2c_s); + return i2c_bit_add_bus(&i2c_s->adapter); +} + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,i2c"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_i2c_driver = { + .driver = { + .name = "litex-i2c", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_i2c_probe, +}; + +module_platform_driver(litex_i2c_driver); + +MODULE_DESCRIPTION("LiteX bitbang I2C Bus driver"); +MODULE_AUTHOR("Antmicro "); From 0b372a1c3f1e8b87808a62b837a0dacf8ab4ab0d Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Sun, 27 Dec 2020 20:52:37 -0500 Subject: [PATCH 08/31] fixup over ("LiteX: driver for I2CMaster") NOTE (gls): this SHOULD work on 32-bit CSRs and/or 64-bit CPUs, but has not been tested under those conditions. --- drivers/i2c/busses/i2c-litex.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/i2c/busses/i2c-litex.c b/drivers/i2c/busses/i2c-litex.c index 1303e6b30c6d5f..7723a59a5ba336 100644 --- a/drivers/i2c/busses/i2c-litex.c +++ b/drivers/i2c/busses/i2c-litex.c @@ -51,16 +51,16 @@ static inline void litex_set_bit(void __iomem *mem, int bit, int val) { u32 regv, new_regv; - regv = litex_get_reg(mem, REGISTER_SIZE); + regv = litex_read8(mem); new_regv = (regv & ~BIT(bit)) | ((!!val) << bit); - litex_set_reg(mem, REGISTER_SIZE, new_regv); + litex_write8(mem, new_regv); } static inline int litex_get_bit(void __iomem *mem, int bit) { u32 regv; - regv = litex_get_reg(mem, REGISTER_SIZE); + regv = litex_read8(mem); return !!(regv & BIT(bit)); } From fb24472f4e9cce8a40526d51748da887bc7a1806 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 22 Dec 2020 16:31:32 -0500 Subject: [PATCH 09/31] LiteX: driver for XADC hwmon Signed-off-by: Robert Winkler FIXME: not updated or tested for 32-bit CSR data width, 64-bit CPU (gls) --- .../devicetree/bindings/hwmon/litex-hwmon.txt | 12 + drivers/hwmon/Kconfig | 6 + drivers/hwmon/Makefile | 1 + drivers/hwmon/litex-hwmon.c | 219 ++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/litex-hwmon.txt create mode 100644 drivers/hwmon/litex-hwmon.c diff --git a/Documentation/devicetree/bindings/hwmon/litex-hwmon.txt b/Documentation/devicetree/bindings/hwmon/litex-hwmon.txt new file mode 100644 index 00000000000000..be2ceba1663cff --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/litex-hwmon.txt @@ -0,0 +1,12 @@ +LiteX XADC hwmon controller + +Required properties: +- compatible: should be "litex,hwmon-xadc" +- reg: base address of configuration registers with length + +Examples: + +hwmon@f0003000 { + compatible = "litex,hwmon-xadc"; + reg = <0x0 0xf0003000 0x0 0x20>; +}; diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 54f04e61fb8364..430a262d52d356 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1180,6 +1180,12 @@ config SENSORS_ADCXX This driver can also be built as a module. If so, the module will be called adcxx. +config SENSORS_LITEX_HWMON + tristate "LiteX XADC hardware monitor" + depends on OF && HAS_IOMEM && LITEX_SOC_CONTROLLER + help + This enables hardware monitoring using LiteX XADC. + config SENSORS_LM63 tristate "National Semiconductor LM63 and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index fe38e8a5c979ae..0b947c2f4b7ff7 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -97,6 +97,7 @@ obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o +obj-$(CONFIG_SENSORS_LITEX_HWMON) += litex-hwmon.o obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o obj-$(CONFIG_SENSORS_LM63) += lm63.o obj-$(CONFIG_SENSORS_LM70) += lm70.o diff --git a/drivers/hwmon/litex-hwmon.c b/drivers/hwmon/litex-hwmon.c new file mode 100644 index 00000000000000..f154765a68aded --- /dev/null +++ b/drivers/hwmon/litex-hwmon.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#define TEMP_REG_OFFSET 0x0 +#define TEMP_REG_SIZE 2 +#define VCCINT_REG_OFFSET 0x8 +#define VCCINT_REG_SIZE 2 +#define VCCAUX_REG_OFFSET 0x10 +#define VCCAUX_REG_SIZE 2 +#define VCCBRAM_REG_OFFSET 0x18 +#define VCCBRAM_REG_SIZE 2 + +#define CHANNEL_TEMP 0 +#define CHANNEL_VCCINT 0 +#define CHANNEL_VCCAUX 1 +#define CHANNEL_VCCBRAM 2 + +struct litex_hwmon { + void __iomem *membase; + struct device *hdev; +}; + +/* Transfer functions taken from XILINX UG480 (v1.10.1) + * www.xilinx.com/support/documentation/user_guides/ug480_7Series_XADC.pdf + */ + +static inline long litex_temp_transfer_fun(long val) +{ + return ((val * 503975ULL) / 4096ULL) - 273150ULL; +} + +static inline long litex_supp_transfer_fun(long val) +{ + return ((val * 3000) / 4096); +} + +static inline int litex_read_temp(struct litex_hwmon *hwmon_s, u32 attr, + int channel, long *val) +{ + unsigned long raw_data; + + if (attr != hwmon_temp_input) + return -ENOTSUPP; + + if (channel != CHANNEL_TEMP) + return -EINVAL; + + raw_data = litex_get_reg(hwmon_s->membase + TEMP_REG_OFFSET, TEMP_REG_SIZE); + *val = litex_temp_transfer_fun(raw_data); + return 0; +} + +static inline int litex_read_in(struct litex_hwmon *hwmon_s, u32 attr, + int channel, long *val) +{ + int offset; + int size; + unsigned long raw_data; + + if (attr != hwmon_in_input) + return -ENOTSUPP; + + switch (channel) { + case CHANNEL_VCCINT: + offset = VCCINT_REG_OFFSET; + size = VCCINT_REG_SIZE; + break; + case CHANNEL_VCCAUX: + offset = VCCAUX_REG_OFFSET; + size = VCCAUX_REG_SIZE; + break; + case CHANNEL_VCCBRAM: + offset = VCCBRAM_REG_OFFSET; + size = VCCBRAM_REG_SIZE; + break; + default: + return -EINVAL; + } + + raw_data = litex_get_reg(hwmon_s->membase + offset, size); + *val = litex_supp_transfer_fun(raw_data); + return 0; +} + +/* API functions */ + +umode_t litex_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +int litex_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct litex_hwmon *hwmon_s = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + return litex_read_temp(hwmon_s, attr, channel, val); + case hwmon_in: + return litex_read_in(hwmon_s, attr, channel, val); + default: + return -ENOTSUPP; + } + + return 0; +} + +static const struct hwmon_ops litex_hwmon_ops = { + .is_visible = litex_hwmon_is_visible, + .read = litex_hwmon_read, +}; + +/* Attribute management */ + +static const unsigned int litex_temp_config[] = { + HWMON_T_INPUT, + 0 +}; + +static const struct hwmon_channel_info litex_hwmon_temp = { + .type = hwmon_temp, + .config = litex_temp_config +}; + +static const unsigned int litex_vcc_config[] = { + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + 0 +}; + +static const struct hwmon_channel_info litex_hwmon_vcc = { + .type = hwmon_in, + .config = litex_vcc_config +}; + +static const struct hwmon_channel_info *litex_hwmon_channel_info[] = { + &litex_hwmon_temp, + &litex_hwmon_vcc, + NULL +}; + +static const struct hwmon_chip_info litex_chip_info = { + .ops = &litex_hwmon_ops, + .info = litex_hwmon_channel_info +}; + +/* Driver functions */ + +static int litex_hwmon_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litex_hwmon *hwmon_s; + struct resource *res; + + if (!node) + return -ENODEV; + + hwmon_s = devm_kzalloc(&pdev->dev, sizeof(*hwmon_s), GFP_KERNEL); + if (!hwmon_s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + hwmon_s->membase = devm_of_iomap(&pdev->dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(hwmon_s->membase)) + return -EIO; + + hwmon_s->hdev = devm_hwmon_device_register_with_info(&pdev->dev, + "litex_xadc", + hwmon_s, + &litex_chip_info, + NULL); + platform_set_drvdata(pdev, hwmon_s); + return PTR_ERR_OR_ZERO(hwmon_s->hdev); +} + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,hwmon-xadc"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_hwmon_driver = { + .driver = { + .name = "litex-hwmon", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_hwmon_probe, +}; + +module_platform_driver(litex_hwmon_driver); From 66d8a9e3a170b2244b673a6ec149bca77cf2203a Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Sun, 27 Dec 2020 20:51:22 -0500 Subject: [PATCH 10/31] fixup over ("LiteX: driver for XADC hwmon") FIXME: still needs register offsets re-calculated based on subreg width! --- drivers/hwmon/litex-hwmon.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/litex-hwmon.c b/drivers/hwmon/litex-hwmon.c index f154765a68aded..c73ac6223c89bf 100644 --- a/drivers/hwmon/litex-hwmon.c +++ b/drivers/hwmon/litex-hwmon.c @@ -66,7 +66,7 @@ static inline int litex_read_temp(struct litex_hwmon *hwmon_s, u32 attr, if (channel != CHANNEL_TEMP) return -EINVAL; - raw_data = litex_get_reg(hwmon_s->membase + TEMP_REG_OFFSET, TEMP_REG_SIZE); + raw_data = litex_read16(hwmon_s->membase + TEMP_REG_OFFSET); *val = litex_temp_transfer_fun(raw_data); return 0; } @@ -75,7 +75,6 @@ static inline int litex_read_in(struct litex_hwmon *hwmon_s, u32 attr, int channel, long *val) { int offset; - int size; unsigned long raw_data; if (attr != hwmon_in_input) @@ -84,21 +83,18 @@ static inline int litex_read_in(struct litex_hwmon *hwmon_s, u32 attr, switch (channel) { case CHANNEL_VCCINT: offset = VCCINT_REG_OFFSET; - size = VCCINT_REG_SIZE; break; case CHANNEL_VCCAUX: offset = VCCAUX_REG_OFFSET; - size = VCCAUX_REG_SIZE; break; case CHANNEL_VCCBRAM: offset = VCCBRAM_REG_OFFSET; - size = VCCBRAM_REG_SIZE; break; default: return -EINVAL; } - raw_data = litex_get_reg(hwmon_s->membase + offset, size); + raw_data = litex_read16(hwmon_s->membase + offset); *val = litex_supp_transfer_fun(raw_data); return 0; } From 2b4bc43b97d3a93cd8a7cf7ad61f2faddfc82a9f Mon Sep 17 00:00:00 2001 From: Daniel Craviee Date: Tue, 22 Dec 2020 16:33:07 -0500 Subject: [PATCH 11/31] LiteX: driver for PWM Signed-off-by: Daniel Craviee FIXME: not updated or tested for 32-bit CSR data width, 64-bit CPU (gls) --- .../devicetree/bindings/pwm/litex,pwm.txt | 19 ++ drivers/pwm/Kconfig | 8 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-litex.c | 178 ++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/litex,pwm.txt create mode 100644 drivers/pwm/pwm-litex.c diff --git a/Documentation/devicetree/bindings/pwm/litex,pwm.txt b/Documentation/devicetree/bindings/pwm/litex,pwm.txt new file mode 100644 index 00000000000000..403de8b69b963f --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/litex,pwm.txt @@ -0,0 +1,19 @@ +LiteX PWM controller + +Required properties: +- compatible: should be "litex,pwm". +- reg: base address and length of the register set for this device. +- clock: frequency of the main clock. +- #pwm-cells: should be 6. See pwm.txt in this directory for a description of + the cells format. + + +Examples: + +pwm@f0003000 { + compatible = "litex,pwm"; + reg = <0x0 0xf0003000 0x0 0x24>; + clock = <100000000>; + #pwm-cells = <6>; + status = "okay"; +}; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 9a4f66ae8070da..60280bfc160ded 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -291,6 +291,14 @@ config PWM_KEEMBAY To compile this driver as a module, choose M here: the module will be called pwm-keembay. +config PWM_LITEX + tristate "LiteX PWM support" + depends on OF + depends on HAS_IOMEM + depends on LITEX_SOC_CONTROLLER + help + Generic PWM framework driver for LiteX + config PWM_LP3943 tristate "TI/National Semiconductor LP3943 PWM support" depends on MFD_LP3943 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 6374d3b1d6f344..db3e4dab23f710 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_PWM_INTEL_LGM) += pwm-intel-lgm.o obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o +obj-$(CONFIG_PWM_LITEX) += pwm-litex.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o diff --git a/drivers/pwm/pwm-litex.c b/drivers/pwm/pwm-litex.c new file mode 100644 index 00000000000000..8f37de34d5c0ac --- /dev/null +++ b/drivers/pwm/pwm-litex.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_EN_ENABLE 0x1 +#define REG_EN_DISABLE 0x0 + +#define ENABLE_REG_OFFSET 0x0 +#define WIDTH_REG_OFFSET 0x4 +#define PERIOD_REG_OFFSET 0x14 + +struct litex_pwm_chip { + struct pwm_chip chip; + unsigned int clock; + void __iomem *base; + void __iomem *width; + void __iomem *period; + void __iomem *enable; +}; + +#define to_litex_pwm_chip(_chip) container_of(_chip, struct litex_pwm_chip, chip) + +static int litex_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct litex_pwm_chip *litex = to_litex_pwm_chip(chip); + unsigned long period_cycles, duty_cycles; + unsigned long long c; + + /* Calculate period cycles */ + c = (unsigned long long)litex->clock * (unsigned long long)period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = c; + + /* Calculate duty cycles */ + c *= duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + /* Apply values to registers */ + litex_set_reg(litex->width, 4, duty_cycles); + litex_set_reg(litex->period, 4, period_cycles); + + return 0; +} + +static int litex_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct litex_pwm_chip *litex = to_litex_pwm_chip(chip); + + litex_set_reg(litex->enable, 1, REG_EN_ENABLE); + return 0; +} + +static void litex_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct litex_pwm_chip *litex = to_litex_pwm_chip(chip); + + litex_set_reg(litex->enable, 1, REG_EN_DISABLE); +} + +static const struct pwm_ops litex_pwm_ops = { + .config = litex_pwm_config, + .enable = litex_pwm_enable, + .disable = litex_pwm_disable, + .owner = THIS_MODULE, +}; + +static int litex_pwm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litex_pwm_chip *litex; + struct resource *res; + int ret; + + if (!node) { + dev_err(&pdev->dev, "Fail on obtaining device node\n"); + return -ENOMEM; + } + + litex = devm_kzalloc(&pdev->dev, sizeof(*litex), GFP_KERNEL); + + if (!litex) { + dev_err(&pdev->dev, "Fail on memory allocation\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Device is busy\n"); + return -EBUSY; + } + + litex->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(litex->base)) { + dev_err(&pdev->dev, "Fail to get base address\n"); + return -EIO; + } + + ret = of_property_read_u32(node, "clock", &(litex->clock)); + if (ret < 0) { + dev_err(&pdev->dev, "No clock record in the dts file\n"); + return -ENODEV; + } + + litex->width = litex->base + WIDTH_REG_OFFSET; + litex->period = litex->base + PERIOD_REG_OFFSET; + litex->enable = litex->base + ENABLE_REG_OFFSET; + + litex->chip.dev = &pdev->dev; + litex->chip.ops = &litex_pwm_ops; + litex->chip.base = -1; + litex->chip.npwm = 1; + + ret = pwmchip_add(&litex->chip); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to add pwm chip %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, litex); + + return 0; +} + +static int litex_pwm_remove(struct platform_device *pdev) +{ + struct litex_pwm_chip *litex = platform_get_drvdata(pdev); + + return pwmchip_remove(&litex->chip); +} + +static const struct of_device_id litex_of_match[] = { + { .compatible = "litex,pwm" }, + {} +}; +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_pwm_driver = { + .driver = { + .name = "litex-pwm", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_pwm_probe, + .remove = litex_pwm_remove, +}; +module_platform_driver(litex_pwm_driver); + +MODULE_DESCRIPTION("LiteX PWM driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); From e75f40ed82a220038e24eb4e5a694c7dad59b508 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Sun, 27 Dec 2020 21:00:01 -0500 Subject: [PATCH 12/31] fixup over ("LiteX: driver for PWM") FIXME: still needs register offsets re-calculated based on subreg width! --- drivers/pwm/pwm-litex.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-litex.c b/drivers/pwm/pwm-litex.c index 8f37de34d5c0ac..f2644f22ca6720 100644 --- a/drivers/pwm/pwm-litex.c +++ b/drivers/pwm/pwm-litex.c @@ -65,8 +65,8 @@ static int litex_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, duty_cycles = c; /* Apply values to registers */ - litex_set_reg(litex->width, 4, duty_cycles); - litex_set_reg(litex->period, 4, period_cycles); + litex_write32(litex->width, duty_cycles); + litex_write32(litex->period, period_cycles); return 0; } @@ -75,7 +75,7 @@ static int litex_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct litex_pwm_chip *litex = to_litex_pwm_chip(chip); - litex_set_reg(litex->enable, 1, REG_EN_ENABLE); + litex_write8(litex->enable, REG_EN_ENABLE); return 0; } @@ -83,7 +83,7 @@ static void litex_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct litex_pwm_chip *litex = to_litex_pwm_chip(chip); - litex_set_reg(litex->enable, 1, REG_EN_DISABLE); + litex_write8(litex->enable, REG_EN_DISABLE); } static const struct pwm_ops litex_pwm_ops = { From eff56640cb91ca73096ff703f8fc32e3621c9f16 Mon Sep 17 00:00:00 2001 From: Daniel Craviee Date: Tue, 22 Dec 2020 16:34:40 -0500 Subject: [PATCH 13/31] LiteX: driver for SPI Flash (mtd) device Signed-off-by: Daniel Craviee FIXME: not updated or tested for 32-bit CSR data width, 64-bit CPU (gls) --- .../bindings/mtd/litex,spiflash.yaml | 41 ++ drivers/mtd/spi-nor/Kconfig | 8 + drivers/mtd/spi-nor/Makefile | 2 + drivers/mtd/spi-nor/litex-spiflash.c | 402 ++++++++++++++++++ 4 files changed, 453 insertions(+) create mode 100644 Documentation/devicetree/bindings/mtd/litex,spiflash.yaml create mode 100644 drivers/mtd/spi-nor/litex-spiflash.c diff --git a/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml b/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml new file mode 100644 index 00000000000000..b92544488b608a --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0 + +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/serial/litex,spiflash.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LiteX SPI Flash controller + +maintainers: + - Karol Gugala + - Mateusz Holenko + +description: | + LiteX SPI Flash controller is a part of LiteX FPGA SoC builder. It supports + multiple CPU architectures, currently including e.g. OpenRISC and RISC-V. + +properties: + compatible: + const: litex,spiflash + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + +examples: + - | + spiflash: spiflash@f0005800 { + compatible = "litex,spiflash"; + reg = <0xf0005800 0xc>; + flash: flash@0 { + compatible = "jedec,spi-nor"; + reg = <0x0 0x1000000>; + }; + }; \ No newline at end of file diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 24cd25de2b8b71..95530ad0de2b73 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -70,4 +70,12 @@ endchoice source "drivers/mtd/spi-nor/controllers/Kconfig" +config SPI_FLASH_LITEX + tristate "LiteX SPI Flash support" + depends on OF + depends on HAS_IOMEM + depends on LITEX_SOC_CONTROLLER + help + Generic SPI Flash driver for LiteX + endif # MTD_SPI_NOR diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index 653923896205c2..12f680e6fae625 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -20,3 +20,5 @@ spi-nor-objs += xmc.o obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o obj-$(CONFIG_MTD_SPI_NOR) += controllers/ + +obj-$(CONFIG_SPI_FLASH_LITEX) += litex-spiflash.o diff --git a/drivers/mtd/spi-nor/litex-spiflash.c b/drivers/mtd/spi-nor/litex-spiflash.c new file mode 100644 index 00000000000000..058983533d1bcd --- /dev/null +++ b/drivers/mtd/spi-nor/litex-spiflash.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPIFLASH_BITBANG_OFFSET 0x0 +#define SPIFLASH_BITBANG_SIZE 0x1 +#define SPIFLASH_MISO_OFFSET 0x4 +#define SPIFLASH_MISO_SIZE 0x1 +#define SPIFLASH_BITBANG_EN_OFFSET 0x8 +#define SPIFLASH_BITBANG_EN_SIZE 0x1 + +#define SPIFLASH_ENABLE 0x01 +#define SPIFLASH_DISABLE 0x00 +#define CLK_ENABLE 0x02 +#define CS_ENABLE 0x04 +#define MISO_MODE 0x08 + +#define WRITE_ENABLE 0x06 +#define READ_STATUS_REGISTER 0x05 +#define WORK_IN_PROGRESS 0x01 +#define READ_FLAG_STATUS_REGISTER 0x70 +#define PROGRAM_ERR 0x10 +#define ERASE_BUSY 0x80 +#define ERASE_ERR 0x20 + +#define TIMEOUT_ERASE_MS 3000 +#define TIMEOUT_MS 50 +#define BIT_SHIFT 7 +#define ADDRESS_SIZE 3 +#define DUMMY_CYCLES 8 + +struct spi { + struct spi_nor nor; + struct device *dev; + void __iomem *base; + struct clk *clk; +}; + +static void cs(struct spi_nor *nor, u8 new_val) +{ + struct spi *spi = nor->priv; + u8 curr_val = (u8)litex_get_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE); + u8 set_val = new_val == CS_ENABLE ? + curr_val | new_val : curr_val & new_val; + + litex_set_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE, set_val); +} + +static void clk(struct spi_nor *nor, u8 new_val) +{ + struct spi *spi = nor->priv; + u8 curr_val = (u8)litex_get_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE); + u8 set_val = new_val == CLK_ENABLE ? + curr_val | new_val : curr_val & new_val; + + litex_set_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE, set_val); +} + +static u8 miso_read(struct spi_nor *nor) +{ + struct spi *spi = nor->priv; + + return (u8)(litex_get_reg(spi->base + SPIFLASH_MISO_OFFSET, SPIFLASH_MISO_SIZE) & 0x1); +} + +static void mosi_set(bool mosi, struct spi_nor *nor) +{ + struct spi *spi = nor->priv; + u8 curr_val = (u8)litex_get_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE); + u8 set_val = mosi ? curr_val | (0x01) : curr_val & (~0x01); + + litex_set_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE, set_val); +} + +static void enable(struct spi_nor *nor, u8 new_val) +{ + struct spi *spi = nor->priv; + u8 curr_val = (u8)litex_get_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE); + u8 set_val = new_val == MISO_MODE ? + curr_val | new_val : curr_val & new_val; + + litex_set_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE, set_val); +} + +static void initial_config(struct spi_nor *nor) +{ + struct spi *spi = nor->priv; + + clk(nor, ~CLK_ENABLE); + cs(nor, CS_ENABLE); + enable(nor, ~MISO_MODE); + litex_set_reg(spi->base + SPIFLASH_BITBANG_EN_OFFSET, SPIFLASH_BITBANG_EN_SIZE, SPIFLASH_ENABLE); +} + +static void dummy_cycles(struct spi_nor *nor, int n_cycles) +{ + int i; + + for (i = 0; i < n_cycles; i++) { + clk(nor, ~CLK_ENABLE); + clk(nor, CLK_ENABLE); + } +} + +static void spi_bitbang_send(struct spi_nor *nor, const u8 command) +{ + int i; + u8 c = command; + + for (i = BIT_SHIFT; i >= 0; i--) { + // Sending on MSB order + mosi_set(c & 0x80, nor); + c <<= 1; + clk(nor, ~CLK_ENABLE); + clk(nor, CLK_ENABLE); + } +} + +static u8 spi_bitbang_read(struct spi_nor *nor) +{ + int i; + u8 byte = 0x00; + + for (i = BIT_SHIFT; i >= 0; i--) { + clk(nor, ~CLK_ENABLE); + clk(nor, CLK_ENABLE); + byte |= (miso_read(nor) << i); + } + return byte; +} + +static void write_command(struct spi_nor *nor, const u8 command) +{ + enable(nor, ~MISO_MODE); + dummy_cycles(nor, DUMMY_CYCLES); + cs(nor, ~CS_ENABLE); + spi_bitbang_send(nor, command); +} + +static void write_address(struct spi_nor *nor, const u32 addr32) +{ + int i; + u8 *addr8 = (u8 *) &addr32; + + for (i = (ADDRESS_SIZE - 1); i >= 0; i--) + spi_bitbang_send(nor, *(addr8 + i)); +} + +static void write_data(struct spi_nor *nor, const u8 *data, int len) +{ + int i; + + enable(nor, ~MISO_MODE); + for (i = 0; i < len; i++) + spi_bitbang_send(nor, data[i]); +} + +static void read_data(struct spi_nor *nor, u8 *buffer, int len) +{ + int i; + + enable(nor, MISO_MODE); + for (i = 0; i < len; i++) + buffer[i] = spi_bitbang_read(nor); +} + +static int spi_flash_nor_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, + size_t len) +{ + write_command(nor, opcode); + read_data(nor, buf, len); + cs(nor, CS_ENABLE); + return 0; +} + +static u8 read_status(struct spi_nor *nor, const u8 status_command) +{ + u8 status; + + spi_flash_nor_read_reg(nor, status_command, &status, 1); + return status; +} + +static int busy(struct spi_nor *nor, int time, u8 reg, u8 flag) +{ + unsigned long end = jiffies + msecs_to_jiffies(time); + /* Wait until device is ready */ + while (read_status(nor, reg) & flag) { + if (time_after(jiffies, end)) + return -ETIMEDOUT; + }; + + return 0; +} + +static int spi_flash_nor_erase(struct spi_nor *nor, loff_t offs) +{ + write_command(nor, WRITE_ENABLE); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + write_command(nor, nor->erase_opcode); + write_address(nor, offs); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_ERASE_MS, READ_FLAG_STATUS_REGISTER, ERASE_BUSY) != 0) + return -ETIMEDOUT; + + if(busy(nor, TIMEOUT_ERASE_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + return (read_status(nor, READ_FLAG_STATUS_REGISTER) & ERASE_ERR); +} + +static ssize_t spi_flash_nor_read(struct spi_nor *nor, loff_t from, + size_t length, u8 *buffer) +{ + write_command(nor, nor->read_opcode); + write_address(nor, from); + read_data(nor, buffer, length); + cs(nor, CS_ENABLE); + + return length; +} + +static ssize_t spi_flash_nor_write(struct spi_nor *nor, loff_t to, size_t len, + const u8 *buf) +{ + /* Write WRITE ENABLE command*/ + write_command(nor, WRITE_ENABLE); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + write_command(nor, nor->program_opcode); + write_address(nor, to); + write_data(nor, buf, len); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + if (read_status(nor, READ_FLAG_STATUS_REGISTER)&PROGRAM_ERR) + return -EINVAL; + + return len; +} + +static ssize_t spi_flash_nor_write_reg(struct spi_nor *nor, u8 opcode, + const u8 *buf, size_t len) +{ + /* Write WRITE ENABLE command*/ + write_command(nor, WRITE_ENABLE); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + write_command(nor, opcode); + write_data(nor, buf, len); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + return 0; +} + +static const struct spi_nor_controller_ops litex_spi_controller_ops = { + .read = spi_flash_nor_read, + .write = spi_flash_nor_write, + .read_reg = spi_flash_nor_read_reg, + .write_reg = spi_flash_nor_write_reg, + .erase = spi_flash_nor_erase, +}; + +static int litex_spi_flash_probe(struct platform_device *pdev) +{ + struct device_node *node; + struct resource *res; + int ret; + struct spi *spi; + struct spi_nor *nor; + + const struct spi_nor_hwcaps hwcaps = { + .mask = SNOR_HWCAPS_READ | + SNOR_HWCAPS_READ_FAST | + SNOR_HWCAPS_PP, + }; + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "No DT found\n"); + return -EINVAL; + } + + spi = devm_kzalloc(&pdev->dev, sizeof(*spi), GFP_KERNEL); + if (!spi) + return -ENOMEM; + platform_set_drvdata(pdev, spi); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spi->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(spi->base)) + return PTR_ERR(spi->base); + + spi->dev = &pdev->dev; + + /* Gets attached flash */ + node = of_get_next_available_child(pdev->dev.of_node, NULL); + if (!node) { + dev_err(&pdev->dev, "no SPI flash device to configure\n"); + ret = -ENODEV; + } + nor = &spi->nor; + nor->dev = spi->dev; + nor->priv = spi; + spi_nor_set_flash_node(nor, node); + /* Sets initial configuration of registers */ + initial_config(nor); + /* Fill the hooks to spi nor */ + nor->controller_ops = &litex_spi_controller_ops; + nor->mtd.name = "spi"; + + ret = spi_nor_scan(nor, NULL, &hwcaps); + if (ret) { + dev_err(&pdev->dev, "SPI_NOR_SCAN FAILED\n"); + return ret; + } + + ret = mtd_device_register(&nor->mtd, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Fail to register device\n"); + return ret; + } + + return 0; +} + +static int litex_spi_flash_remove(struct platform_device *pdev) +{ + struct spi *spi = platform_get_drvdata(pdev); + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spi->base = devm_ioremap_resource(&pdev->dev, res); + litex_set_reg(spi->base + SPIFLASH_BITBANG_EN_OFFSET, SPIFLASH_BITBANG_EN_SIZE, SPIFLASH_DISABLE); + mtd_device_unregister(&spi->nor.mtd); + + return 0; +} + +static const struct of_device_id litex_of_match[] = { + { .compatible = "litex,spiflash" }, + {} +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_spi_flash_driver = { + .probe = litex_spi_flash_probe, + .remove = litex_spi_flash_remove, + .driver = { + .name = "litex-spiflash", + .of_match_table = of_match_ptr(litex_of_match) + }, +}; +module_platform_driver(litex_spi_flash_driver); + +MODULE_DESCRIPTION("LiteX SPI Flash driver"); +MODULE_AUTHOR("Antmicro "); From e1e96eb487465882e3687769cdf034ea9c81a380 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Sun, 27 Dec 2020 20:55:56 -0500 Subject: [PATCH 14/31] fixup over ("LiteX: driver for SPI Flash (mtd) device") NOTE (gls): this SHOULD work on 32-bit CSRs and/or 64-bit CPUs, but has not been tested under those conditions. --- drivers/mtd/spi-nor/litex-spiflash.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/mtd/spi-nor/litex-spiflash.c b/drivers/mtd/spi-nor/litex-spiflash.c index 058983533d1bcd..82744962d9381c 100644 --- a/drivers/mtd/spi-nor/litex-spiflash.c +++ b/drivers/mtd/spi-nor/litex-spiflash.c @@ -68,47 +68,47 @@ struct spi { static void cs(struct spi_nor *nor, u8 new_val) { struct spi *spi = nor->priv; - u8 curr_val = (u8)litex_get_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE); + u8 curr_val = litex_read8(spi->base + SPIFLASH_BITBANG_OFFSET); u8 set_val = new_val == CS_ENABLE ? curr_val | new_val : curr_val & new_val; - litex_set_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE, set_val); + litex_write8(spi->base + SPIFLASH_BITBANG_OFFSET, set_val); } static void clk(struct spi_nor *nor, u8 new_val) { struct spi *spi = nor->priv; - u8 curr_val = (u8)litex_get_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE); + u8 curr_val = litex_read8(spi->base + SPIFLASH_BITBANG_OFFSET); u8 set_val = new_val == CLK_ENABLE ? curr_val | new_val : curr_val & new_val; - litex_set_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE, set_val); + litex_write8(spi->base + SPIFLASH_BITBANG_OFFSET, set_val); } static u8 miso_read(struct spi_nor *nor) { struct spi *spi = nor->priv; - return (u8)(litex_get_reg(spi->base + SPIFLASH_MISO_OFFSET, SPIFLASH_MISO_SIZE) & 0x1); + return litex_read8(spi->base + SPIFLASH_MISO_OFFSET) & 0x1; } static void mosi_set(bool mosi, struct spi_nor *nor) { struct spi *spi = nor->priv; - u8 curr_val = (u8)litex_get_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE); + u8 curr_val = litex_read8(spi->base + SPIFLASH_BITBANG_OFFSET); u8 set_val = mosi ? curr_val | (0x01) : curr_val & (~0x01); - litex_set_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE, set_val); + litex_write8(spi->base + SPIFLASH_BITBANG_OFFSET, set_val); } static void enable(struct spi_nor *nor, u8 new_val) { struct spi *spi = nor->priv; - u8 curr_val = (u8)litex_get_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE); + u8 curr_val = litex_read8(spi->base + SPIFLASH_BITBANG_OFFSET); u8 set_val = new_val == MISO_MODE ? curr_val | new_val : curr_val & new_val; - litex_set_reg(spi->base + SPIFLASH_BITBANG_OFFSET, SPIFLASH_BITBANG_SIZE, set_val); + litex_write8(spi->base + SPIFLASH_BITBANG_OFFSET, set_val); } static void initial_config(struct spi_nor *nor) @@ -118,7 +118,7 @@ static void initial_config(struct spi_nor *nor) clk(nor, ~CLK_ENABLE); cs(nor, CS_ENABLE); enable(nor, ~MISO_MODE); - litex_set_reg(spi->base + SPIFLASH_BITBANG_EN_OFFSET, SPIFLASH_BITBANG_EN_SIZE, SPIFLASH_ENABLE); + litex_write8(spi->base + SPIFLASH_BITBANG_EN_OFFSET, SPIFLASH_ENABLE); } static void dummy_cycles(struct spi_nor *nor, int n_cycles) @@ -375,7 +375,7 @@ static int litex_spi_flash_remove(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); spi->base = devm_ioremap_resource(&pdev->dev, res); - litex_set_reg(spi->base + SPIFLASH_BITBANG_EN_OFFSET, SPIFLASH_BITBANG_EN_SIZE, SPIFLASH_DISABLE); + litex_write8(spi->base + SPIFLASH_BITBANG_EN_OFFSET, SPIFLASH_DISABLE); mtd_device_unregister(&spi->nor.mtd); return 0; From ddff59820917fd93c8e05cce2f6d4fee4661a135 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 26 Mar 2021 17:11:06 +0100 Subject: [PATCH 15/31] dt-bindings: mtd: Fix litex,spiflash - Fix $id to match file path, - Include spi-controller.yaml, - Document flash subnode, - Add missing "unevaluatedProperties: false", - Fix indentation of example, - Use "spi" from generic node names recommendation, - Add missing #{address,size}-cells to example, - Fix reg property of flash subnode. Signed-off-by: Geert Uytterhoeven --- .../bindings/mtd/litex,spiflash.yaml | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml b/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml index b92544488b608a..e688584c27f83f 100644 --- a/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml +++ b/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml @@ -2,7 +2,7 @@ %YAML 1.2 --- -$id: http://devicetree.org/schemas/serial/litex,spiflash.yaml# +$id: http://devicetree.org/schemas/mtd/litex,spiflash.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# title: LiteX SPI Flash controller @@ -15,6 +15,9 @@ description: | LiteX SPI Flash controller is a part of LiteX FPGA SoC builder. It supports multiple CPU architectures, currently including e.g. OpenRISC and RISC-V. +allOf: + - $ref: "/schemas/spi/spi-controller.yaml#" + properties: compatible: const: litex,spiflash @@ -29,13 +32,26 @@ required: - compatible - reg +patternProperties: + "flash@[0-9a-f]+$": + type: object + properties: + compatible: + enum: + - jedec,spi-nor + +unevaluatedProperties: false + examples: - | - spiflash: spiflash@f0005800 { - compatible = "litex,spiflash"; - reg = <0xf0005800 0xc>; - flash: flash@0 { - compatible = "jedec,spi-nor"; - reg = <0x0 0x1000000>; - }; - }; \ No newline at end of file + spiflash: spi@f0005800 { + compatible = "litex,spiflash"; + reg = <0xf0005800 0xc>; + #address-cells = <1>; + #size-cells = <0>; + + flash: flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + }; + }; From 464aaeea6a1e4f1d1539b23c9fade60f815d1b2c Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 22 Dec 2020 16:36:35 -0500 Subject: [PATCH 16/31] LiteX: driver for ICAPBitstream fpga manager Signed-off-by: Robert Winkler FIXME: not updated or tested for 32-bit CSR data width, 64-bit CPU (gls) --- .../devicetree/bindings/fpga/litex-fpga.txt | 12 ++ drivers/fpga/Kconfig | 6 + drivers/fpga/Makefile | 1 + drivers/fpga/litex-fpga.c | 200 ++++++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 Documentation/devicetree/bindings/fpga/litex-fpga.txt create mode 100644 drivers/fpga/litex-fpga.c diff --git a/Documentation/devicetree/bindings/fpga/litex-fpga.txt b/Documentation/devicetree/bindings/fpga/litex-fpga.txt new file mode 100644 index 00000000000000..5e9c4893ddc453 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/litex-fpga.txt @@ -0,0 +1,12 @@ +LiteX ICAPBitstream fpga manager + +Required properties: +- compatible: should be "litex,fpga-icap" +- reg: base address of configuration registers with length + +Examples: + +fpga_man: icap@f0007000 { + compatible = "litex,fpga-icap"; + reg = <0x0 0xf0007000 0x0 0x14>; +}; diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 5ff9438b7b4611..53dec5274b1360 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -91,6 +91,12 @@ config FPGA_MGR_TS73XX FPGA manager driver support for the Altera Cyclone II FPGA present on the TS-73xx SBC boards. +config FPGA_MGR_LITEX + tristate "LiteX ICAPBitstream FPGA Manager" + depends on OF && HAS_IOMEM && LITEX_SOC_CONTROLLER + help + FPGA Manager for LiteX SoC builder that uses ICAPBitstream. + config FPGA_BRIDGE tristate "FPGA Bridge Framework" help diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 18dc9885883a29..9d61ed0c3d99f8 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o obj-$(CONFIG_FPGA_MGR_ALTERA_CVP) += altera-cvp.o obj-$(CONFIG_FPGA_MGR_ALTERA_PS_SPI) += altera-ps-spi.o obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o +obj-$(CONFIG_FPGA_MGR_LITEX) += litex-fpga.o obj-$(CONFIG_FPGA_MGR_MACHXO2_SPI) += machxo2-spi.o obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o diff --git a/drivers/fpga/litex-fpga.c b/drivers/fpga/litex-fpga.c new file mode 100644 index 00000000000000..1b3cbc54323107 --- /dev/null +++ b/drivers/fpga/litex-fpga.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OFFSET_REG_SINK_DATA 0x0 +#define OFFSET_REG_SINK_READY 0x10 + +#define REG_SINK_DATA_SIZE 0x4 +#define REG_SINK_READY_SIZE 0x1 + +#define INITIAL_HEADER_SIZE -1 /* Set to maximum value */ +#define ALLOWED_FPGA_MGR_FLAGS (FPGA_MGR_PARTIAL_RECONFIG | \ + FPGA_MGR_COMPRESSED_BITSTREAM) +#define BITSTREAM_INSTR_SIZE sizeof(uint32_t) + +/* Macros for accessing ICAP registers */ + +#define WRITE_SINK_DATA(mem, val) litex_set_reg(mem + OFFSET_REG_SINK_DATA, \ + REG_SINK_DATA_SIZE, val) +#define READ_SINK_READY(mem) litex_get_reg(mem + OFFSET_REG_SINK_READY, \ + REG_SINK_READY_SIZE) + +struct litex_fpga { + void __iomem *membase; +}; + +/* Helper functions */ + +static inline bool bit_has_sync(uint8_t *buf, size_t count) +{ + int i; + + for (i = 0; i < count - BITSTREAM_INSTR_SIZE; i += BITSTREAM_INSTR_SIZE) + /* Sync word is 0xAA995566 */ + if (buf[i] == 0xAA && + buf[i + 1] == 0x99 && + buf[i + 2] == 0x55 && + buf[i + 3] == 0x66) + return true; + return false; +} + +static inline bool bit_is_aligned(uint8_t *buf, size_t count) +{ + return !(count % BITSTREAM_INSTR_SIZE); +} + +/* API functions */ + +static enum fpga_mgr_states litex_fpga_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static int litex_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + /* Check if driver supports given operations */ + if (info->flags & ~ALLOWED_FPGA_MGR_FLAGS) { + dev_err(&mgr->dev, "Unsupported bitstream flags occurred\n"); + return -EINVAL; + } + + return 0; +} + +static int litex_fpga_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + const struct litex_fpga *fpga_s = (const struct litex_fpga *) mgr->priv; + uint32_t *buf32; + int i, count32; + + /* Bitstream should consist of 32bit words*/ + if (!bit_is_aligned((uint8_t *) buf, count)) { + dev_err(&mgr->dev, "Invalid bitstream alignment\n"); + return -EINVAL; + } + + /* Correct bitstream contains sync word */ + if (!bit_has_sync((uint8_t *) buf, count)) { + dev_err(&mgr->dev, "Bitstream has no sync word\n"); + return -EINVAL; + } + + buf32 = (uint32_t *) buf; + count32 = count / BITSTREAM_INSTR_SIZE; + for (i = 0; i < count32; ++i) { + while (!READ_SINK_READY(fpga_s->membase)) + ; + WRITE_SINK_DATA(fpga_s->membase, be32_to_cpu(buf32[i])); + } + + return 0; +} + +static int litex_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + return 0; +} + +static const struct fpga_manager_ops litex_fpga_manager_ops = { + .initial_header_size = INITIAL_HEADER_SIZE, + .state = litex_fpga_state, + .write_init = litex_fpga_write_init, + .write = litex_fpga_write, + .write_complete = litex_fpga_write_complete, +}; + +/* Driver functions */ + +static int litex_fpga_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr; + + mgr = platform_get_drvdata(pdev); + fpga_mgr_unregister(mgr); + + return 0; +} + +static int litex_fpga_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litex_fpga *fpga_s; + struct fpga_manager *mgr; + struct resource *res; + + if (!node) + return -ENODEV; + + fpga_s = devm_kzalloc(&pdev->dev, sizeof(*fpga_s), GFP_KERNEL); + if (!fpga_s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + fpga_s->membase = devm_of_iomap(&pdev->dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(fpga_s->membase)) + return -EIO; + + mgr = devm_fpga_mgr_create(&pdev->dev, + "LiteX ICAPBitstream FPGA Manager", + &litex_fpga_manager_ops, fpga_s); + if (!mgr) + return -ENOMEM; + + platform_set_drvdata(pdev, mgr); + return fpga_mgr_register(mgr); +} + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,fpga-icap"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_fpga_driver = { + .driver = { + .name = "litex-icap-fpga-mgr", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_fpga_probe, + .remove = litex_fpga_remove +}; + +module_platform_driver(litex_fpga_driver); + +MODULE_DESCRIPTION("LiteX ICAPBitstream FPGA Manager driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); From 1de2b5e05d4a97c74dbbe9475848d69261ce2c30 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Sun, 27 Dec 2020 16:22:03 -0500 Subject: [PATCH 17/31] fixup over ("LiteX: driver for ICAPBitstream fpga manager") FIXME: still needs register offsets re-calculated based on subreg width! --- drivers/fpga/litex-fpga.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/fpga/litex-fpga.c b/drivers/fpga/litex-fpga.c index 1b3cbc54323107..c3a4f38a6610e7 100644 --- a/drivers/fpga/litex-fpga.c +++ b/drivers/fpga/litex-fpga.c @@ -39,10 +39,8 @@ /* Macros for accessing ICAP registers */ -#define WRITE_SINK_DATA(mem, val) litex_set_reg(mem + OFFSET_REG_SINK_DATA, \ - REG_SINK_DATA_SIZE, val) -#define READ_SINK_READY(mem) litex_get_reg(mem + OFFSET_REG_SINK_READY, \ - REG_SINK_READY_SIZE) +#define WRITE_SINK_DATA(mem, val) litex_write32(mem + OFFSET_REG_SINK_DATA, val) +#define READ_SINK_READY(mem) litex_read8(mem + OFFSET_REG_SINK_READY) struct litex_fpga { void __iomem *membase; From 36c0889aacdf22be26ef30249151dfa77a3716d2 Mon Sep 17 00:00:00 2001 From: Filip Kokosinski Date: Tue, 22 Dec 2020 16:38:21 -0500 Subject: [PATCH 18/31] LiteX: support for VexRiscV interrupt controller Signed-off-by: Filip Kokosinski FIXME: not updated or tested for 32-bit CSR data width, 64-bit CPU (gls) --- arch/riscv/Kconfig | 1 + drivers/irqchip/Kconfig | 18 +++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-litex-vexriscv.c | 169 +++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 drivers/irqchip/irq-litex-vexriscv.c diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index 87d7b52f278f0c..473ad217a6ca24 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -97,6 +97,7 @@ config RISCV select SYSCTL_EXCEPTION_TRACE select THREAD_INFO_IN_TASK select UACCESS_MEMCPY if !MMU + select HANDLE_DOMAIN_IRQ # FIXME: should go in Kconfigs.soc !!! config ARCH_MMAP_RND_BITS_MIN default 18 if 64BIT diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 15536e321df556..7e107977d31916 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -540,6 +540,24 @@ config LOONGSON_HTPIC select GENERIC_IRQ_CHIP help Support for the Loongson-3 HyperTransport PIC Controller. +config LITEX_VEXRISCV_INTC + bool "LiteX VexRiscv interrupt controller support" + select IRQ_DOMAIN + help + Support for the VexRiscv's interrupt controller. VexRiscv does not follow + RISC-V privileged specification and uses two additional CSRs: mask and + pending. Please refer to LITEX_VEXRISCV_CSR_MASK and + LITEX_VEXRISCV_CSR_PENDING for their respective configuration. + +config LITEX_VEXRISCV_CSR_MASK + depends on LITEX_VEXRISCV_INTC + hex "VexRiscv's mask CSR index." + default 0x9c0 + +config LITEX_VEXRISCV_CSR_PENDING + depends on LITEX_VEXRISCV_INTC + hex "VexRiscv's pending CSR index." + default 0xdc0 config LOONGSON_HTVEC bool "Loongson3 HyperTransport Interrupt Vector Controller" diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c59b95a0532c9a..4a098d1aa69248 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -101,6 +101,7 @@ obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o obj-$(CONFIG_IMX_INTMUX) += irq-imx-intmux.o obj-$(CONFIG_MADERA_IRQ) += irq-madera.o +obj-$(CONFIG_LITEX_VEXRISCV_INTC) += irq-litex-vexriscv.o obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o diff --git a/drivers/irqchip/irq-litex-vexriscv.c b/drivers/irqchip/irq-litex-vexriscv.c new file mode 100644 index 00000000000000..a4fe688dd81986 --- /dev/null +++ b/drivers/irqchip/irq-litex-vexriscv.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VexRiscv interrupt controller driver + * + * Copyright (C) 2019 Antmicro Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#define IRQ_MASK CONFIG_LITEX_VEXRISCV_CSR_MASK +#define IRQ_PENDING CONFIG_LITEX_VEXRISCV_CSR_PENDING +#define IRQ_VEC_SZ 32 + +static struct irq_domain *root_domain; + +struct litex_vexriscv_intc { + struct irq_chip chip; + irq_flow_handler_t handle; + unsigned long flags; +}; + +static unsigned int litex_vexriscv_irq_getie(void) +{ + return (csr_read(sstatus) & IE_SIE) != 0; +} + +static void litex_vexriscv_irq_setie(unsigned int ie) +{ + if (ie) + csr_write(sstatus, IE_SIE); + else + csr_clear(sstatus, IE_SIE); +} + +static unsigned int litex_vexriscv_irq_getmask(void) +{ + u32 mask; + + __asm__ __volatile__ ("csrr %0, %1" : "=r"(mask) : "i"(IRQ_MASK)); + return mask; +} + +static void litex_vexriscv_irq_setmask(unsigned int mask) +{ + __asm__ volatile ("csrw %0, %1" :: "i"(IRQ_MASK), "r"(mask)); +} + +static unsigned int litex_vexriscv_irq_pending(void) +{ + u32 pending; + + __asm__ __volatile__ ("csrr %0, %1" : "=r"(pending) : "i"(IRQ_PENDING)); + return pending; +} + +static int litex_vexriscv_intc_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct litex_vexriscv_intc *intc = d->host_data; + + pr_debug("irqchip: LiteX Vexriscv irqmap: %d\n", irq); + irq_set_chip_and_handler(irq, &intc->chip, intc->handle); + irq_set_status_flags(irq, intc->flags); + + pr_debug("irqchip: mapcount: %d\n", d->mapcount); + + return 0; +} + +static void litex_vexriscv_intc_mask(struct irq_data *data) +{ + unsigned int mask = litex_vexriscv_irq_getmask(); + unsigned int irqbit = BIT(data->hwirq); + + pr_debug("irqchip: LiteX VexRiscv irqchip mask: 0x%08x -> 0x%08x", + mask, mask & ~irqbit); + mask &= ~irqbit; + litex_vexriscv_irq_setmask(mask); + + if (!mask) + litex_vexriscv_irq_setie(0); +} + +static void litex_vexriscv_intc_unmask(struct irq_data *data) +{ + unsigned int mask = litex_vexriscv_irq_getmask(); + unsigned int irqbit = BIT(data->hwirq); + + pr_debug("irqchip: LiteX VexRiscv irqchip mask: 0x%08x -> 0x%08x", + mask, mask | irqbit); + mask |= irqbit; + litex_vexriscv_irq_setmask(mask); + + if (!litex_vexriscv_irq_getie()) + litex_vexriscv_irq_setie(1); +} + +static void litex_vexriscv_intc_ack(struct irq_data *data) +{ + /* no need to ack IRQs */ +} + +static void litex_vexriscv_intc_handle_irq(struct pt_regs *regs) +{ + unsigned int irq; + int i; + + irq = litex_vexriscv_irq_pending() & litex_vexriscv_irq_getmask(); + pr_debug("irqchip: Litex Vexriscv irqchip mask: 0x%08x pending: 0x%08x", + litex_vexriscv_irq_getmask(), + litex_vexriscv_irq_pending()); + + for (i = 0; i < IRQ_VEC_SZ; i++) { + pr_debug("irqchip: i %d, irq 0x%08x, trying 0x%08lx\n", i, irq, BIT(i)); + + if (irq & BIT(i)) { + pr_debug("irqchip: handling at i = %d\n", i); + handle_domain_irq(root_domain, i, regs); + } + } +} + +static const struct irq_domain_ops litex_vexriscv_domain_ops = { + .xlate = irq_domain_xlate_onecell, + .map = litex_vexriscv_intc_map, +}; + +static struct litex_vexriscv_intc litex_vexriscv_intc0 = { + .handle = handle_edge_irq, + .flags = IRQ_TYPE_DEFAULT | IRQ_TYPE_PROBE, + .chip = { + .name = "litex-vexriscv-intc0", + .irq_mask = litex_vexriscv_intc_mask, + .irq_unmask = litex_vexriscv_intc_unmask, + .irq_ack = litex_vexriscv_intc_ack, + }, +}; + +static int __init litex_vexriscv_intc_init(struct device_node *node, struct device_node *parent) +{ + /* clear interrupts for init */ + litex_vexriscv_irq_setie(0); + litex_vexriscv_irq_setmask(0); + + root_domain = irq_domain_add_linear(node, IRQ_VEC_SZ, + &litex_vexriscv_domain_ops, + &litex_vexriscv_intc0); + irq_set_default_host(root_domain); + set_handle_irq(litex_vexriscv_intc_handle_irq); + + /* print device info */ + pr_info("irqchip: LiteX VexRiscv irqchip driver initialized. " + "IE: %d, mask: 0x%08x, pending: 0x%08x\n", + litex_vexriscv_irq_getie(), + litex_vexriscv_irq_getmask(), + litex_vexriscv_irq_pending()); + pr_info("irqchip: LiteX VexRiscv irqchip settings: " + "mask CSR 0x%03x, pending CSR 0x%03x\n", IRQ_MASK, + IRQ_PENDING); + + return 0; +} + +IRQCHIP_DECLARE(litex_vexriscv_intc0, "vexriscv,intc0", litex_vexriscv_intc_init); From 1398ca75a9f8700aaa9b5525b9cbbd38db09e8fa Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 22 Dec 2020 16:40:27 -0500 Subject: [PATCH 19/31] LiteX: driver for LiteVideo Signed-off-by: Robert Winkler Signed-off-by: Filip Kokosinski FIXME: not updated or tested for 32-bit CSR data width, 64-bit CPU (gls) --- .../bindings/gpu/litex,litevideo.txt | 34 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/litevideo/Kconfig | 18 + drivers/gpu/drm/litevideo/Makefile | 3 + drivers/gpu/drm/litevideo/litevideo.c | 315 ++++++++++++++++++ drivers/gpu/drm/litevideo/litevideo.h | 86 +++++ drivers/gpu/drm/litevideo/mmcm.h | 46 +++ 8 files changed, 505 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpu/litex,litevideo.txt create mode 100644 drivers/gpu/drm/litevideo/Kconfig create mode 100644 drivers/gpu/drm/litevideo/Makefile create mode 100644 drivers/gpu/drm/litevideo/litevideo.c create mode 100644 drivers/gpu/drm/litevideo/litevideo.h create mode 100644 drivers/gpu/drm/litevideo/mmcm.h diff --git a/Documentation/devicetree/bindings/gpu/litex,litevideo.txt b/Documentation/devicetree/bindings/gpu/litex,litevideo.txt new file mode 100644 index 00000000000000..a6e8f61f289d24 --- /dev/null +++ b/Documentation/devicetree/bindings/gpu/litex,litevideo.txt @@ -0,0 +1,34 @@ +DRM LiteX LiteVideo driver + +Required properties: +- compatible: should be "litex,litevideo" +- reg: base address of configuration registers with length +- litevideo,pixel-clock: pixel clock frequency in kHz +- litevideo,h-active: horizontal active pixels +- litevideo,h-blanking: total horizontal blanking +- litevideo,h-sync: horizontal sync width +- litevideo,h-front-porch: horizontal front porch +- litevideo,v-active: vertical active pixels +- litevideo,v-blanking: vertical total blanking +- litevideo,v-sync: vertical sync width +- litevideo,v-front-porch: vertical front porch +- litevideo,dma-offset: dma offset in memory +- litevideo,dma-length: size of memory used for storing the image + +Examples: + +litevideo0: gpu@f0009800 { + compatible = "litex,litevideo"; + reg = <0x0 0xf0009800 0x0 0x100>; + litevideo,pixel-clock = <148500>; + litevideo,h-active = <1920>; + litevideo,h-blanking = <280>; + litevideo,h-sync = <44>; + litevideo,h-front-porch = <148>; + litevideo,v-active = <1080>; + litevideo,v-blanking = <45>; + litevideo,v-sync = <5>; + litevideo,v-front-porch = <36>; + litevideo,dma-offset = <0x8000000>; + litevideo,dma-length = <0x7e9000>; +}; diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 85b79a7fee630b..fe790e37645cfd 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -386,6 +386,8 @@ source "drivers/gpu/drm/tidss/Kconfig" source "drivers/gpu/drm/xlnx/Kconfig" +source "drivers/gpu/drm/litevideo/Kconfig" + # Keep legacy drivers last menuconfig DRM_LEGACY diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 926adef289db96..23854a6060b586 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -125,3 +125,4 @@ obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/ obj-$(CONFIG_DRM_MCDE) += mcde/ obj-$(CONFIG_DRM_TIDSS) += tidss/ obj-y += xlnx/ +obj-$(CONFIG_DRM_LITEVIDEO) += litevideo/ diff --git a/drivers/gpu/drm/litevideo/Kconfig b/drivers/gpu/drm/litevideo/Kconfig new file mode 100644 index 00000000000000..27a4c7660d4a9b --- /dev/null +++ b/drivers/gpu/drm/litevideo/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Copyright (C) 2020 Antmicro +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +config DRM_LITEVIDEO + tristate "LiteVideo support" + depends on DRM && LITEX_SOC_CONTROLLER + help + This enables DRM driver for LiteX LiteVideo. diff --git a/drivers/gpu/drm/litevideo/Makefile b/drivers/gpu/drm/litevideo/Makefile new file mode 100644 index 00000000000000..d752ef770bc36c --- /dev/null +++ b/drivers/gpu/drm/litevideo/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_DRM_LITEVIDEO) += litevideo.o diff --git a/drivers/gpu/drm/litevideo/litevideo.c b/drivers/gpu/drm/litevideo/litevideo.c new file mode 100644 index 00000000000000..61d96db32a9caa --- /dev/null +++ b/drivers/gpu/drm/litevideo/litevideo.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "litevideo.h" +#include "mmcm.h" + +#define DRIVER_NAME "litevideo" + +#define DMA_DISABLE 0 +#define DMA_ENABLE 1 + +static void litevideo_get_md(u32 pixel_clock, u32 *best_m, u32 *best_d) +{ + u32 ideal_m, ideal_d, curr_m, curr_d, m, d; + u32 curr_diff, test_diff; + + ideal_m = pixel_clock; + ideal_d = LITEVIDEO_IDEAL_DIV_VALUE; + + /* Start searching from minimum values */ + + curr_m = MMCM_MIN_M; + curr_d = MMCM_MIN_D; + + for (d = MMCM_MIN_D; d < MMCM_MAX_D; d++) { + for (m = MMCM_MIN_M; m < MMCM_MAX_M; m++) { + + /* Clocks cannot be set perfectly, therefore all + * combinations for multiplier (m) and divisor (d) + * are checked to find the closest possible clock value + */ + + curr_diff = abs((d * ideal_d * curr_m) - + (d * curr_d * ideal_m)); + test_diff = abs((curr_d * ideal_d * m) - + (d * curr_d * ideal_m)); + + if (test_diff < curr_diff) { + curr_m = m; + curr_d = d; + } + } + } + + *best_m = curr_m; + *best_d = curr_d; +} + +static int litevideo_mmcm_write(struct litevideo_prv *prv, u32 addr, u32 data) +{ + /* write MMCM register address */ + + litex_set_reg(prv->base + LITEVIDEO_MMCM_ADDR_OFF, + LITEVIDEO_MMCM_ADDR_SIZE, addr); + + /* write data to send to MMCM register */ + + litex_set_reg(prv->base + LITEVIDEO_MMCM_DATA_OFF, + LITEVIDEO_MMCM_DATA_SIZE, data); + + /* send the data */ + + litex_set_reg(prv->base + LITEVIDEO_MMCM_WRITE_OFF, + LITEVIDEO_MMCM_WRITE_SIZE, MMCM_WRITE); + + /* wait for transfer finish */ + + if (!wait_event_timeout(prv->wq, + litex_get_reg(prv->base + LITEVIDEO_MMCM_READY_OFF, + LITEVIDEO_MMCM_READY_SIZE), HZ)) + return -ETIMEDOUT; + + return 0; +} + +static int litevideo_clkgen_write(struct litevideo_prv *prv, u32 m, u32 d) +{ + /* write M */ + + int ret; + + ret = litevideo_mmcm_write(prv, MMCM_CLKFBOUT1, + MMCM_HT_FALLING_EDGE | + (m / 2) << MMCM_HT_SHIFT | + (m / 2 + (m % 2)) << MMCM_LT_SHIFT); + + if (ret < 0) + return ret; + + /* write D */ + + if (d == 1) + ret = litevideo_mmcm_write(prv, MMCM_DIVCLK, + MMCM_HT_FALLING_EDGE); + else + ret = litevideo_mmcm_write(prv, MMCM_DIVCLK, + (d / 2) << MMCM_HT_SHIFT | + (d / 2 + (d % 2)) << MMCM_LT_SHIFT); + + if (ret < 0) + return ret; + + /* clkout0_divide = 10 */ + + ret = litevideo_mmcm_write(prv, MMCM_CLKOUT0, + MMCM_HT_FALLING_EDGE | + MMCM_CLKOUT_DIV10 << MMCM_HT_SHIFT | + MMCM_CLKOUT_DIV10 << MMCM_LT_SHIFT); + + if (ret < 0) + return ret; + + /* clkout1_divide = 2 */ + + ret = litevideo_mmcm_write(prv, MMCM_CLKOUT1, + MMCM_HT_FALLING_EDGE | + MMCM_CLKOUT_DIV2 << MMCM_HT_SHIFT | + MMCM_CLKOUT_DIV2 << MMCM_LT_SHIFT); + + return ret; +} + +static int litevideo_drv_init(struct litevideo_prv *prv, u32 m, u32 d) +{ + int ret; + + /* initialize waitqueue for timeouts in litex_mmcm_write() */ + + init_waitqueue_head(&prv->wq); + + /* generate clock */ + + ret = litevideo_clkgen_write(prv, m, d); + if (ret < 0) + return ret; + + /* timings - horizontal */ + + litex_set_reg(prv->base + LITEVIDEO_CORE_HRES_OFF, + LITEVIDEO_CORE_HRES_SIZE, prv->h_active); + litex_set_reg(prv->base + LITEVIDEO_CORE_HSYNC_START_OFF, + LITEVIDEO_CORE_HSYNC_START_SIZE, + prv->h_active + prv->h_front_porch); + litex_set_reg(prv->base + LITEVIDEO_CORE_HSYNC_END_OFF, + LITEVIDEO_CORE_HSYNC_END_SIZE, + prv->h_active + prv->h_front_porch + prv->v_sync); + litex_set_reg(prv->base + LITEVIDEO_CORE_HSCAN_OFF, + LITEVIDEO_CORE_HSCAN_SIZE, + prv->h_active + prv->h_blanking); + + /* timings - vertical */ + + litex_set_reg(prv->base + LITEVIDEO_CORE_VRES_OFF, + LITEVIDEO_CORE_VRES_SIZE, prv->v_active); + litex_set_reg(prv->base + LITEVIDEO_CORE_VSYNC_START_OFF, + LITEVIDEO_CORE_VSYNC_START_SIZE, + prv->v_active + prv->v_front_porch); + litex_set_reg(prv->base + LITEVIDEO_CORE_VSYNC_END_OFF, + LITEVIDEO_CORE_VSYNC_END_SIZE, + prv->v_active + prv->v_front_porch + prv->v_sync); + litex_set_reg(prv->base + LITEVIDEO_CORE_VSCAN_OFF, + LITEVIDEO_CORE_VSCAN_SIZE, + prv->v_active + prv->v_blanking); + + /* configure DMA */ + + litex_set_reg(prv->base + LITEVIDEO_DMA_ENABLE_OFF, + LITEVIDEO_DMA_ENABLE_SIZE, DMA_DISABLE); + + litex_set_reg(prv->base + LITEVIDEO_DMA_BASE_ADDR_OFF, + LITEVIDEO_DMA_BASE_ADDR_SIZE, prv->dma_offset); + + litex_set_reg(prv->base + LITEVIDEO_DMA_LENGTH_OFF, + LITEVIDEO_DMA_LENGTH_SIZE, prv->dma_length); + + litex_set_reg(prv->base + LITEVIDEO_DMA_ENABLE_OFF, + LITEVIDEO_DMA_ENABLE_SIZE, DMA_ENABLE); + + return 0; +} + +static int litevideo_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct litevideo_prv *prv; + struct resource *res; + int ret; + u32 val; + u32 m, d; + + /* no device tree */ + + if (!np) + return -ENODEV; + + prv = devm_kzalloc(&pdev->dev, sizeof(*prv), GFP_KERNEL); + if (!prv) + return -ENOMEM; + + /* pixel clock */ + + ret = of_property_read_u32(np, "litevideo,pixel-clock", &val); + if (ret) + return -EINVAL; + prv->pixel_clock = val; + + /* timings - vertical */ + + ret = of_property_read_u32(np, "litevideo,v-active", &val); + if (ret) + return -EINVAL; + prv->v_active = val; + + ret = of_property_read_u32(np, "litevideo,v-blanking", &val); + if (ret) + return -EINVAL; + prv->v_blanking = val; + + ret = of_property_read_u32(np, "litevideo,v-front-porch", &val); + if (ret) + return -EINVAL; + prv->v_front_porch = val; + + ret = of_property_read_u32(np, "litevideo,v-sync", &val); + if (ret) + return -EINVAL; + prv->v_sync = val; + + /* timings - horizontal */ + + ret = of_property_read_u32(np, "litevideo,h-active", &val); + if (ret) + return -EINVAL; + prv->h_active = val; + + ret = of_property_read_u32(np, "litevideo,h-blanking", &val); + if (ret) + return -EINVAL; + prv->h_blanking = val; + + ret = of_property_read_u32(np, "litevideo,h-front-porch", &val); + if (ret) + return -EINVAL; + prv->h_front_porch = val; + + ret = of_property_read_u32(np, "litevideo,h-sync", &val); + if (ret) + return -EINVAL; + prv->h_sync = val; + + /* DMA */ + + ret = of_property_read_u32(np, "litevideo,dma-offset", &val); + if (ret) + return -EINVAL; + prv->dma_offset = val; + + ret = of_property_read_u32(np, "litevideo,dma-length", &val); + if (ret) + return -EINVAL; + prv->dma_length = val; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + prv->base = devm_ioremap_resource(&pdev->dev, res); + if (!prv->base) + return -ENXIO; + + litevideo_get_md(prv->pixel_clock, &m, &d); + return litevideo_drv_init(prv, m, d); +} + +static const struct of_device_id litevideo_of_match[] = { + { .compatible = "litex,litevideo" }, + {} +}; +MODULE_DEVICE_TABLE(of, litevideo_of_match); + +static struct platform_driver litevideo_platform_driver = { + .probe = litevideo_probe, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = litevideo_of_match, + }, +}; + +module_platform_driver(litevideo_platform_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("LiteVideo driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/litevideo/litevideo.h b/drivers/gpu/drm/litevideo/litevideo.h new file mode 100644 index 00000000000000..c0fbaaa83d9f50 --- /dev/null +++ b/drivers/gpu/drm/litevideo/litevideo.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2020 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LITEVIDEO_LITEVIDEO_H_ +#define _LITEVIDEO_LITEVIDEO_H_ + +#include +#include +#include +#include + +/* register offsets */ + +#define LITEVIDEO_CORE_HRES_OFF 0x1c +#define LITEVIDEO_CORE_HSYNC_START_OFF 0x24 +#define LITEVIDEO_CORE_HSYNC_END_OFF 0x2c +#define LITEVIDEO_CORE_HSCAN_OFF 0x34 +#define LITEVIDEO_CORE_VRES_OFF 0x3c +#define LITEVIDEO_CORE_VSYNC_START_OFF 0x44 +#define LITEVIDEO_CORE_VSYNC_END_OFF 0x4c +#define LITEVIDEO_CORE_VSCAN_OFF 0x54 + +#define LITEVIDEO_MMCM_WRITE_OFF 0x94 +#define LITEVIDEO_MMCM_READY_OFF 0x98 +#define LITEVIDEO_MMCM_ADDR_OFF 0x9c +#define LITEVIDEO_MMCM_DATA_OFF 0xa0 + +#define LITEVIDEO_DMA_ENABLE_OFF 0x18 +#define LITEVIDEO_DMA_BASE_ADDR_OFF 0x5c +#define LITEVIDEO_DMA_LENGTH_OFF 0x6c + +/* register sizes */ + +#define LITEVIDEO_CORE_HRES_SIZE 2 +#define LITEVIDEO_CORE_HSYNC_START_SIZE 2 +#define LITEVIDEO_CORE_HSYNC_END_SIZE 2 +#define LITEVIDEO_CORE_HSCAN_SIZE 2 +#define LITEVIDEO_CORE_VRES_SIZE 2 +#define LITEVIDEO_CORE_VSYNC_START_SIZE 2 +#define LITEVIDEO_CORE_VSYNC_END_SIZE 2 +#define LITEVIDEO_CORE_VSCAN_SIZE 2 + +#define LITEVIDEO_MMCM_WRITE_SIZE 1 +#define LITEVIDEO_MMCM_READY_SIZE 1 +#define LITEVIDEO_MMCM_ADDR_SIZE 1 +#define LITEVIDEO_MMCM_DATA_SIZE 2 + +#define LITEVIDEO_DMA_ENABLE_SIZE 1 +#define LITEVIDEO_DMA_BASE_ADDR_SIZE 4 +#define LITEVIDEO_DMA_LENGTH_SIZE 4 + +/* constants */ + +/* clk100 has to be used for LiteX VideoOut */ +#define LITEVIDEO_IDEAL_DIV_VALUE 10000 + +struct litevideo_prv { + struct drm_device *drm_dev; + void __iomem *base; + struct clk *hdmi_clk; + wait_queue_head_t wq; + u32 h_active; + u32 h_blanking; + u32 h_front_porch; + u32 h_sync; + u32 v_active; + u32 v_blanking; + u32 v_front_porch; + u32 v_sync; + u32 dma_offset; + u32 dma_length; + u32 pixel_clock; +}; + +#endif /* _LITEVIDEO_LITEVIDEO_H_ */ diff --git a/drivers/gpu/drm/litevideo/mmcm.h b/drivers/gpu/drm/litevideo/mmcm.h new file mode 100644 index 00000000000000..de25c5a0ac955b --- /dev/null +++ b/drivers/gpu/drm/litevideo/mmcm.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2020 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LITEVIDEO_MMCM_H_ +#define _LITEVIDEO_MMCM_H_ + +/* mmcm addresses */ + +#define MMCM_CLKFBOUT1 0x14 +#define MMCM_DIVCLK 0x16 +#define MMCM_CLKOUT0 0x08 +#define MMCM_CLKOUT1 0x0a + +/* predefined values and shifts */ + +#define MMCM_HT_FALLING_EDGE 0x1000 +#define MMCM_HT_SHIFT 6 +#define MMCM_LT_SHIFT 0 + +/* This values should be set both to HIGH_TIME and LOW_TIME in CLKOUTx + * - clk division time = 2*VALUE // (i.e. MMCM_CLKOUT_DIV10) + * - duty = 50% // (HIGH_TIME / (HIGH_TIME + LOW_TIME)) + */ + +#define MMCM_CLKOUT_DIV10 5 +#define MMCM_CLKOUT_DIV2 1 + +#define MMCM_WRITE 1 + +#define MMCM_MIN_M 2 +#define MMCM_MAX_M 128 +#define MMCM_MIN_D 1 +#define MMCM_MAX_D 128 + +#endif /* _LITEVIDEO_MMCM_H_ */ From e79fcccdb444bc6c999543c326f7773e1b3108e5 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Sun, 27 Dec 2020 20:49:37 -0500 Subject: [PATCH 20/31] fixup over ("LiteX: driver for LiteVideo") FIXME: still needs register offsets re-calculated based on subreg width! --- drivers/gpu/drm/litevideo/litevideo.c | 55 ++++++++++----------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/drivers/gpu/drm/litevideo/litevideo.c b/drivers/gpu/drm/litevideo/litevideo.c index 61d96db32a9caa..f028a8fa883d6e 100644 --- a/drivers/gpu/drm/litevideo/litevideo.c +++ b/drivers/gpu/drm/litevideo/litevideo.c @@ -73,24 +73,21 @@ static int litevideo_mmcm_write(struct litevideo_prv *prv, u32 addr, u32 data) { /* write MMCM register address */ - litex_set_reg(prv->base + LITEVIDEO_MMCM_ADDR_OFF, - LITEVIDEO_MMCM_ADDR_SIZE, addr); + litex_write8(prv->base + LITEVIDEO_MMCM_ADDR_OFF, addr); /* write data to send to MMCM register */ - litex_set_reg(prv->base + LITEVIDEO_MMCM_DATA_OFF, - LITEVIDEO_MMCM_DATA_SIZE, data); + litex_write16(prv->base + LITEVIDEO_MMCM_DATA_OFF, data); /* send the data */ - litex_set_reg(prv->base + LITEVIDEO_MMCM_WRITE_OFF, - LITEVIDEO_MMCM_WRITE_SIZE, MMCM_WRITE); + litex_write8(prv->base + LITEVIDEO_MMCM_WRITE_OFF, MMCM_WRITE); /* wait for transfer finish */ if (!wait_event_timeout(prv->wq, - litex_get_reg(prv->base + LITEVIDEO_MMCM_READY_OFF, - LITEVIDEO_MMCM_READY_SIZE), HZ)) + litex_read8(prv->base + LITEVIDEO_MMCM_READY_OFF), + HZ)) return -ETIMEDOUT; return 0; @@ -159,45 +156,33 @@ static int litevideo_drv_init(struct litevideo_prv *prv, u32 m, u32 d) /* timings - horizontal */ - litex_set_reg(prv->base + LITEVIDEO_CORE_HRES_OFF, - LITEVIDEO_CORE_HRES_SIZE, prv->h_active); - litex_set_reg(prv->base + LITEVIDEO_CORE_HSYNC_START_OFF, - LITEVIDEO_CORE_HSYNC_START_SIZE, + litex_write16(prv->base + LITEVIDEO_CORE_HRES_OFF, + prv->h_active); + litex_write16(prv->base + LITEVIDEO_CORE_HSYNC_START_OFF, prv->h_active + prv->h_front_porch); - litex_set_reg(prv->base + LITEVIDEO_CORE_HSYNC_END_OFF, - LITEVIDEO_CORE_HSYNC_END_SIZE, + litex_write16(prv->base + LITEVIDEO_CORE_HSYNC_END_OFF, prv->h_active + prv->h_front_porch + prv->v_sync); - litex_set_reg(prv->base + LITEVIDEO_CORE_HSCAN_OFF, - LITEVIDEO_CORE_HSCAN_SIZE, + litex_write16(prv->base + LITEVIDEO_CORE_HSCAN_OFF, prv->h_active + prv->h_blanking); /* timings - vertical */ - litex_set_reg(prv->base + LITEVIDEO_CORE_VRES_OFF, - LITEVIDEO_CORE_VRES_SIZE, prv->v_active); - litex_set_reg(prv->base + LITEVIDEO_CORE_VSYNC_START_OFF, - LITEVIDEO_CORE_VSYNC_START_SIZE, + litex_write16(prv->base + LITEVIDEO_CORE_VRES_OFF, + prv->v_active); + litex_write16(prv->base + LITEVIDEO_CORE_VSYNC_START_OFF, prv->v_active + prv->v_front_porch); - litex_set_reg(prv->base + LITEVIDEO_CORE_VSYNC_END_OFF, - LITEVIDEO_CORE_VSYNC_END_SIZE, + litex_write16(prv->base + LITEVIDEO_CORE_VSYNC_END_OFF, prv->v_active + prv->v_front_porch + prv->v_sync); - litex_set_reg(prv->base + LITEVIDEO_CORE_VSCAN_OFF, - LITEVIDEO_CORE_VSCAN_SIZE, + litex_write16(prv->base + LITEVIDEO_CORE_VSCAN_OFF, prv->v_active + prv->v_blanking); /* configure DMA */ - litex_set_reg(prv->base + LITEVIDEO_DMA_ENABLE_OFF, - LITEVIDEO_DMA_ENABLE_SIZE, DMA_DISABLE); - - litex_set_reg(prv->base + LITEVIDEO_DMA_BASE_ADDR_OFF, - LITEVIDEO_DMA_BASE_ADDR_SIZE, prv->dma_offset); - - litex_set_reg(prv->base + LITEVIDEO_DMA_LENGTH_OFF, - LITEVIDEO_DMA_LENGTH_SIZE, prv->dma_length); - - litex_set_reg(prv->base + LITEVIDEO_DMA_ENABLE_OFF, - LITEVIDEO_DMA_ENABLE_SIZE, DMA_ENABLE); + litex_write8(prv->base + LITEVIDEO_DMA_ENABLE_OFF, DMA_DISABLE); + // FIXME: gateware and this call should be updated to 64-bit base addr! + litex_write32(prv->base + LITEVIDEO_DMA_BASE_ADDR_OFF, prv->dma_offset); + litex_write32(prv->base + LITEVIDEO_DMA_LENGTH_OFF, prv->dma_length); + litex_write8(prv->base + LITEVIDEO_DMA_ENABLE_OFF, DMA_ENABLE); return 0; } From 33f271c53bec4ce71b91a855ee494325f12515e0 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 22 Dec 2020 16:41:51 -0500 Subject: [PATCH 21/31] LiteX: driver for MMCM Signed-off-by: Pawel Czarnecki Signed-off-by: Mateusz Holenko FIXME: not updated or tested for 32-bit CSR data width, 64-bit CPU (gls) --- .../bindings/clock/litex,clock.yaml | 232 +++ drivers/clk/Kconfig | 6 + drivers/clk/Makefile | 1 + drivers/clk/clk-litex.c | 1786 +++++++++++++++++ drivers/clk/clk-litex.h | 132 ++ 5 files changed, 2157 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/litex,clock.yaml create mode 100644 drivers/clk/clk-litex.c create mode 100644 drivers/clk/clk-litex.h diff --git a/Documentation/devicetree/bindings/clock/litex,clock.yaml b/Documentation/devicetree/bindings/clock/litex,clock.yaml new file mode 100644 index 00000000000000..cd234fa4132af8 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/litex,clock.yaml @@ -0,0 +1,232 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/litex,clock.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LiteX clock control driver + +description: | + Common clock driver with MMCM unit for dynamic reconfiguration + of up to 7 clock outputs with ability to change frequency, duty + cycle and phase offset + +maintainers: + - Karol Gugala + - Mateusz Holenko + +properties: + compatible: + const: litex,clock + + reg: + description: Base address and lengths of the register space + + "#clock-cells": + description: | + Number of cells in a clock specifier; + Typically 0 for nodes with a single clock output + and 1 for nodes with multiple clock outputs. + + const: 1 + + "#address-cells": + description: | + Number of cells that are needed to form the base address + part in the reg property. + + const: 1 + + "#size-cells": + description: | + Used to state how many cells are in each field of a reg property + + const: 0 + + clock-output-names: + description: | + List of strings of clock output signal names indexed + by the first cell in the clock specifier. + + minItems: 1 + maxItems: 7 + items: + - const: CLKOUT0 + - const: CLKOUT1 + - const: CLKOUT2 + - const: CLKOUT3 + - const: CLKOUT4 + - const: CLKOUT5 + - const: CLKOUT6 + + litex,nclkout: + description: | + Number of desired clock outputs + + min: 1 + max: 7 + + litex,lock-timeout: + description: | + Number of ms to wait for MMCM to assert LOCK signal + + min: 1 + + litex,drdy-timeout: + description: | + Number of ms to wait for MMCM to assert DRDY signal + + min: 1 + + litex,sys-clock-freq: + description: | + System clock frequency + + CLKOUTx: + description: | + Child node representing configurable clock outputs of MMCM unit + + properties: + compatible: + const: litex,clock + + reg: + description: | + clock output ID, zero-based numbering + + litex,clock-frequency: + description: | + default frequency in Hz for clock output + + min: 1587000 + max: 100000000 + + litex,clock-phase: + description: | + default phase offset given in degrees + + min: 0 + max: 359 + + litex,clock-duty-num: + description: | + default duty cycle numerator value + + min: 1 + max: 100 + + litex,clock-duty-den: + description: | + default duty cycle denominator value + + min: 1 + max: 100 + + required: + - compatible + - "#clock-cells" + - clock-output-names + - reg + - litex,clock-frequency + - litex,clock-phase + - litex,clock-duty-num + - litex,clock-duty-den + +required: + - compatible + - reg + - "#clock-cells" + - "#address-cells" + - "#size-cells" + - clock-output-names + - litex,nclkout + - CLKOUTx + +examples: + - | + clk0: clk@f0003000 { + compatible = "litex,clk"; + reg = <0x0 0xf0003000 0x0 0x100>; + #clock-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + clock-output-names = "CLKOUT0", + "CLKOUT1", + "CLKOUT2", + "CLKOUT3", + "CLKOUT4", + "CLKOUT5", + "CLKOUT6"; + litex,nclkout = <7>; + + CLKOUT0: CLKOUT0 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT0"; + reg = <0>; + litex,clock-frequency = <50000000>; + litex,clock-phase = <0>; + litex,clock-duty = <50>; + }; + + CLKOUT1: CLKOUT1 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT1"; + reg = <1>; + litex,clock-frequency = <50000000>; + litex,clock-phase = <90>; + litex,clock-duty = <50>; + }; + + CLKOUT2: CLKOUT2 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT2"; + reg = <2>; + litex,clock-frequency = <25000000>; + litex,clock-phase = <0>; + litex,clock-duty = <25>; + }; + + CLKOUT3: CLKOUT3 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT3"; + reg = <3>; + litex,clock-frequency = <12500000>; + litex,clock-phase = <0>; + litex,clock-duty = <75>; + }; + + CLKOUT4: CLKOUT4 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT4"; + reg = <4>; + litex,clock-frequency = <6250000>; + litex,clock-phase = <0>; + litex,clock-duty = <50>; + }; + + CLKOUT5: CLKOUT5 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT5"; + reg = <5>; + litex,clock-frequency = <3125000>; + litex,clock-phase = <0>; + litex,clock-duty = <50>; + }; + + CLKOUT6: CLKOUT6 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT6"; + reg = <6>; + litex,clock-frequency = <1562500>; + litex,clock-phase = <0>; + litex,clock-duty = <5>; + }; + }; +... diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index a588d56502d40a..6898b3564d10ae 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -376,6 +376,12 @@ config COMMON_CLK_K210 help Support for the Canaan Kendryte K210 RISC-V SoC clocks. +config COMMON_CLK_LITEX + tristate "LiteX Clock control support" + depends on COMMON_CLK && OF && LITEX_SOC_CONTROLLER + help + Clock control support for LiteX SoC builder, utilising MMCM unit and DRP registers + source "drivers/clk/actions/Kconfig" source "drivers/clk/analogbits/Kconfig" source "drivers/clk/baikal-t1/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index b22ae4f81e0b33..e6d0a0e8cb0fb1 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_MACH_ASPEED_G6) += clk-ast2600.o obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o obj-$(CONFIG_CLK_HSDK) += clk-hsdk-pll.o obj-$(CONFIG_COMMON_CLK_K210) += clk-k210.o +obj-$(CONFIG_COMMON_CLK_LITEX) += clk-litex.o obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.o obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o diff --git a/drivers/clk/clk-litex.c b/drivers/clk/clk-litex.c new file mode 100644 index 00000000000000..196ea56392c893 --- /dev/null +++ b/drivers/clk/clk-litex.c @@ -0,0 +1,1786 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020 Antmicro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ +#include "clk-litex.h" +#include +#include +#include +#include +#include + +struct litex_drp_reg { + u32 offset; + u32 size; +}; + +struct litex_drp_reg drp[] = { + {DRP_OF_RESET, DRP_SIZE_RESET}, + {DRP_OF_READ, DRP_SIZE_READ}, + {DRP_OF_WRITE, DRP_SIZE_WRITE}, + {DRP_OF_DRDY, DRP_SIZE_DRDY}, + {DRP_OF_ADR, DRP_SIZE_ADR}, + {DRP_OF_DAT_W, DRP_SIZE_DAT_W}, + {DRP_OF_DAT_R, DRP_SIZE_DAT_R}, + {DRP_OF_LOCKED, DRP_SIZE_LOCKED}, +}; + +struct litex_clk_device { + void __iomem *base; + int is_clkout; + struct clk_hw clk_hw; + struct litex_clk_clkout *clkouts; + u32 nclkout; + u32 lock_timeout; + u32 drdy_timeout; + u32 sys_clk_freq; +}; + +struct litex_clk_clkout_addr { + u8 reg1; + u8 reg2; +}; + +struct litex_clk_regs_addr { + struct litex_clk_clkout_addr clkout[CLKOUT_MAX]; +}; + +struct litex_clk_clkout { + void __iomem *base; + int is_clkout; + struct clk_hw clk_hw; + u32 id; + u32 default_freq; + u32 default_phase; + u32 default_duty_num; + u32 default_duty_den; + u32 lock_timeout; + u32 drdy_timeout; + u32 sys_clk_freq; +}; + +struct litex_clk_regs_addr litex_clk_regs_addr_init(void) +{ + struct litex_clk_regs_addr m; + u32 i, addr; + + addr = CLKOUT0_REG1; + for (i = 0; i <= CLKOUT_MAX; i++) { + if (i == 5) { + /* + *special case because CLKOUT5 have its reg addresses + *placed lower than other CLKOUTs + */ + m.clkout[5].reg1 = CLKOUT5_REG1; + m.clkout[5].reg2 = CLKOUT5_REG2; + } else { + m.clkout[i].reg1 = addr; + addr++; + m.clkout[i].reg2 = addr; + addr++; + } + } + return m; +} + +/* + * This code is taken from: + * https://github.com/torvalds/linux/blob/master/drivers/clk/clk-axi-clkgen.c + * + * Copyright 2012-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * FIXME: make this code common + */ + +/* MMCM loop filter lookup table */ +static u32 litex_clk_lookup_filter(u32 glob_mul) +{ + switch (glob_mul) { + case 0: + return 0x01001990; + case 1: + return 0x01001190; + case 2: + return 0x01009890; + case 3: + return 0x01001890; + case 4: + return 0x01008890; + case 5 ... 8: + return 0x01009090; + case 9 ... 11: + return 0x01000890; + case 12: + return 0x08009090; + case 13 ... 22: + return 0x01001090; + case 23 ... 36: + return 0x01008090; + case 37 ... 46: + return 0x08001090; + default: + return 0x08008090; + } +} + +/* MMCM lock detection lookup table */ +static const u32 litex_clk_lock_table[] = { + 0x060603e8, 0x060603e8, 0x080803e8, 0x0b0b03e8, + 0x0e0e03e8, 0x111103e8, 0x131303e8, 0x161603e8, + 0x191903e8, 0x1c1c03e8, 0x1f1f0384, 0x1f1f0339, + 0x1f1f02ee, 0x1f1f02bc, 0x1f1f028a, 0x1f1f0271, + 0x1f1f023f, 0x1f1f0226, 0x1f1f020d, 0x1f1f01f4, + 0x1f1f01db, 0x1f1f01c2, 0x1f1f01a9, 0x1f1f0190, + 0x1f1f0190, 0x1f1f0177, 0x1f1f015e, 0x1f1f015e, + 0x1f1f0145, 0x1f1f0145, 0x1f1f012c, 0x1f1f012c, + 0x1f1f012c, 0x1f1f0113, 0x1f1f0113, 0x1f1f0113, +}; + +/* Helper function for lock lookup table */ +static u32 litex_clk_lookup_lock(u32 glob_mul) +{ + if (glob_mul < ARRAY_SIZE(litex_clk_lock_table)) + return litex_clk_lock_table[glob_mul]; + return 0x1f1f00fa; +} +/* End of copied code */ + +static inline struct litex_clk_device *clk_hw_to_litex_clk_device + (struct clk_hw *clk_hw) +{ + return container_of(clk_hw, struct litex_clk_device, clk_hw); +} + +static inline struct litex_clk_clkout *clk_hw_to_litex_clk_clkout + (struct clk_hw *clk_hw) +{ + return container_of(clk_hw, struct litex_clk_clkout, clk_hw); +} + +/* Clock control driver functions */ +static inline void litex_clk_set_reg(struct clk_hw *clk_hw, u32 reg, u32 val) +{ + struct litex_clk_clkout *l_clk = clk_hw_to_litex_clk_clkout(clk_hw); + + litex_set_reg(l_clk->base + drp[reg].offset, drp[reg].size, val); +} + +static inline u32 litex_clk_get_reg(struct clk_hw *clk_hw, u32 reg) +{ + struct litex_clk_clkout *l_clk = clk_hw_to_litex_clk_clkout(clk_hw); + + return litex_get_reg(l_clk->base + drp[reg].offset, drp[reg].size); +} + +static inline void litex_clk_assert_reg(struct clk_hw *clk_hw, u32 reg) +{ + int assert = (1 << (drp[reg].size * BITS_PER_BYTE)) - 1; + + litex_clk_set_reg(clk_hw, reg, assert); +} + +static inline void litex_clk_deassert_reg(struct clk_hw *clk_hw, u32 reg) +{ + litex_clk_set_reg(clk_hw, reg, ZERO_REG); +} + +static int litex_clk_wait_lock(struct clk_hw *clk_hw) +{ + struct litex_clk_clkout *l_clk; + u32 timeout; + + l_clk = clk_hw_to_litex_clk_clkout(clk_hw); + + /* Check if clk_hw is global or clkout specific */ + if (l_clk->is_clkout) { + timeout = l_clk->lock_timeout; + } else { + /* clk hw is global */ + struct litex_clk_device *l_dev; + + l_dev = clk_hw_to_litex_clk_device(clk_hw); + timeout = l_dev->lock_timeout; + } + + /*Waiting for LOCK signal to assert in MMCM*/ + while (!litex_clk_get_reg(clk_hw, DRP_LOCKED) && timeout) { + timeout--; + usleep_range(900, 1000); + } + if (timeout == 0) { + pr_err("CLKOUT%d: %s timeout", l_clk->id, __func__); + return -ETIME; + } + return 0; +} + +static int litex_clk_wait_drdy(struct clk_hw *clk_hw) +{ + struct litex_clk_clkout *l_clk; + u32 timeout; + + l_clk = clk_hw_to_litex_clk_clkout(clk_hw); + + /* Check if clk_hw is global or clkout specific */ + if (l_clk->is_clkout) { + timeout = l_clk->drdy_timeout; + } else { + /* clk hw is global */ + struct litex_clk_device *l_dev; + + l_dev = clk_hw_to_litex_clk_device(clk_hw); + timeout = l_dev->drdy_timeout; + } + + /*Waiting for DRDY signal to assert in MMCM*/ + while (!litex_clk_get_reg(clk_hw, DRP_DRDY) && timeout) { + timeout--; + usleep_range(900, 1000); + } + if (timeout == 0) { + pr_err("CLKOUT%d: %s timeout", l_clk->id, __func__); + return -ETIME; + } + return 0; +} + +/* Read value written in given internal MMCM register*/ +static int litex_clk_get_DO(struct clk_hw *clk_hw, u8 clk_reg_addr, u16 *res) +{ + int ret; + + litex_clk_set_reg(clk_hw, DRP_ADR, clk_reg_addr); + litex_clk_assert_reg(clk_hw, DRP_READ); + + litex_clk_deassert_reg(clk_hw, DRP_READ); + ret = litex_clk_wait_drdy(clk_hw); + if (ret != 0) + return ret; + + *res = litex_clk_get_reg(clk_hw, DRP_DAT_R); + + return 0; +} + +/* Return global divider and multiplier values */ +static int litex_clk_get_global_divider(struct clk_hw *hw, u32 *divider, + u32 *multiplier) +{ + int ret; + u16 divreg, mult; + u8 low_time, high_time; + + ret = litex_clk_get_DO(hw, CLKFBOUT_REG1, &mult); + if (ret != 0) + return ret; + ret = litex_clk_get_DO(hw, DIV_REG, &divreg); + if (ret != 0) + return ret; + + low_time = mult & HL_TIME_MASK; + high_time = (mult >> HIGH_TIME_POS) & HL_TIME_MASK; + *multiplier = low_time + high_time; + + low_time = divreg & HL_TIME_MASK; + high_time = (divreg >> HIGH_TIME_POS) & HL_TIME_MASK; + *divider = low_time + high_time; + + return 0; +} + +/* Calculate frequency after global divider and multiplier */ +static u32 litex_clk_get_real_global_frequency(struct clk_hw *hw) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u32 g_divider, g_multiplier; + + litex_clk_get_global_divider(hw, &g_divider, &g_multiplier); + return l_clkout->sys_clk_freq * g_multiplier / g_divider; +} + +/* Calculate frequency after global divider and multiplier without clk_hw */ +static u32 litex_clk_get_expctd_global_frequency(u32 sys_clk_freq) +{ + u32 g_divider, g_multiplier; + + g_divider = (GLOB_DIV_VAL & HL_TIME_MASK) + + ((GLOB_DIV_VAL >> HIGH_TIME_POS) & HL_TIME_MASK); + + g_multiplier = (GLOB_MUL_VAL & HL_TIME_MASK) + + ((GLOB_MUL_VAL >> HIGH_TIME_POS) & HL_TIME_MASK); + + return sys_clk_freq * g_multiplier / g_divider; +} +/* Return dividers of given CLKOUT */ +static int litex_clk_get_clkout_divider(struct clk_hw *hw, u32 *divider, + u32 *fract_cnt) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + int ret; + u16 div, frac; + u8 clkout_nr = l_clkout->id; + u8 low_time, high_time; + + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg1, &div); + if (ret != 0) + return ret; + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg2, &frac); + if (ret != 0) + return ret; + + low_time = div & HL_TIME_MASK; + high_time = (div >> HIGH_TIME_POS) & HL_TIME_MASK; + *divider = low_time + high_time; + *fract_cnt = (frac >> FRAC_POS) & FRAC_MASK; + + return 0; +} + +/* Debug functions */ +#ifdef DEBUG +static void litex_clk_print_general_regs(struct clk_hw *clk_hw) +{ + u16 power_reg, div_reg, clkfbout_reg, lock_reg1, + lock_reg2, lock_reg3, filt_reg1, filt_reg2; + + litex_clk_get_DO(clk_hw, POWER_REG, &power_reg); + litex_clk_get_DO(clk_hw, DIV_REG, &div_reg); + litex_clk_get_DO(clk_hw, CLKFBOUT_REG1, &clkfbout_reg); + litex_clk_get_DO(clk_hw, LOCK_REG1, &lock_reg1); + litex_clk_get_DO(clk_hw, LOCK_REG2, &lock_reg2); + litex_clk_get_DO(clk_hw, LOCK_REG3, &lock_reg3); + litex_clk_get_DO(clk_hw, FILT_REG1, &filt_reg1); + litex_clk_get_DO(clk_hw, FILT_REG2, &filt_reg2); + + pr_debug("POWER REG: 0x%x\n", power_reg); + pr_debug("DIV REG: 0x%x\n", div_reg); + pr_debug("MUL REG: 0x%x\n", clkfbout_reg); + pr_debug("LOCK_REG1: 0x%x\n", lock_reg1); + pr_debug("LOCK_REG2: 0x%x\n", lock_reg2); + pr_debug("LOCK_REG3: 0x%x\n", lock_reg3); + pr_debug("FILT_REG1: 0x%x\n", filt_reg1); + pr_debug("FILT_REG2: 0x%x\n", filt_reg2); +} + +static void litex_clk_print_clkout_regs(struct clk_hw *clk_hw, u8 clkout, + u8 reg1, u8 reg2) +{ + u16 clkout_reg1, clkout_reg2; + + litex_clk_get_DO(clk_hw, reg1, &clkout_reg1); + pr_debug("CLKOUT%d REG1: 0x%x\n", clkout, clkout_reg1); + + litex_clk_get_DO(clk_hw, reg1, &clkout_reg2); + pr_debug("CLKOUT%d REG2: 0x%x\n", clkout, clkout_reg2); +} + +static void litex_clk_print_all_regs(struct clk_hw *clk_hw) +{ + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + u32 i; + + pr_debug("General regs:\n"); + litex_clk_print_general_regs(clk_hw); + for (i = 0; i < CLKOUT_MAX; i++) + litex_clk_print_clkout_regs(clk_hw, i, drp_addr.clkout[i].reg1, + drp_addr.clkout[i].reg2); +} +#endif /* #ifdef DEBUG */ + +/* Return raw value ready to be written into DRP */ +static inline u16 litex_clk_calc_DI(u16 DO_val, u16 mask, u16 bitset) +{ + u16 DI_val; + + DI_val = DO_val & mask; + DI_val |= bitset; + + return DI_val; +} + +/* Sets calculated DI value into DI DRP register */ +static int litex_clk_set_DI(struct clk_hw *clk_hw, u16 DI_val) +{ + int ret; + + litex_clk_set_reg(clk_hw, DRP_DAT_W, DI_val); + litex_clk_assert_reg(clk_hw, DRP_WRITE); + litex_clk_deassert_reg(clk_hw, DRP_WRITE); + ret = litex_clk_wait_drdy(clk_hw); + if (ret != 0) + return ret; + return 0; +} + +/* + * Change register value as specified in arguments + * + * clk_hw: hardware specific clock structure for selecting CLKOUT + * mask: preserve or zero MMCM register bits + * by selecting 1 or 0 on desired specific mask positions + * bitset: set those bits in MMCM register which are 1 in bitset + * clk_reg_addr: internal MMCM address of control register + * + */ +static int litex_clk_change_value(struct clk_hw *clk_hw, u16 mask, u16 bitset, + u8 clk_reg_addr) +{ + u16 DO_val, DI_val; + int ret; + + litex_clk_assert_reg(clk_hw, DRP_RESET); + + ret = litex_clk_get_DO(clk_hw, clk_reg_addr, &DO_val); + if (ret != 0) + return ret; + DI_val = litex_clk_calc_DI(DO_val, mask, bitset); + ret = litex_clk_set_DI(clk_hw, DI_val); + if (ret != 0) + return ret; +#ifdef DEBUG + DI_val = litex_clk_get_reg(clk_hw, DRP_DAT_W); +#endif + litex_clk_deassert_reg(clk_hw, DRP_DAT_W); + litex_clk_deassert_reg(clk_hw, DRP_RESET); + ret = litex_clk_wait_lock(clk_hw); + if (ret != 0) + return ret; + + pr_debug("set 0x%x under addr: 0x%x", DI_val, clk_reg_addr); + return 0; +} + +/* + * Set register values for given CLKOUT + * + * clk_hw: hardware specific clock structure for selecting CLKOUT + * clkout_nr: clock output number + * mask_regX: preserve or zero MMCM register X bits + * by selecting 1 or 0 on desired specific mask positions + * bitset_regX: set those bits in MMCM register X which are 1 in bitset + * + */ +static int litex_clk_set_clock(struct clk_hw *clk_hw, u8 clkout_nr, + u16 mask_reg1, u16 bitset_reg1, + u16 mask_reg2, u16 bitset_reg2) +{ + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + int ret; + + if (!(mask_reg2 == KEEP_REG_16 && bitset_reg2 == ZERO_REG)) { + ret = litex_clk_change_value(clk_hw, mask_reg2, bitset_reg2, + drp_addr.clkout[clkout_nr].reg2); + if (ret != 0) + return ret; + } + if (!(mask_reg1 == KEEP_REG_16 && bitset_reg1 == ZERO_REG)) { + ret = litex_clk_change_value(clk_hw, mask_reg1, bitset_reg1, + drp_addr.clkout[clkout_nr].reg1); + if (ret != 0) + return ret; + } + return 0; +} + +/* + * Set global divider for all CLKOUTs + * + * clk_hw: hardware specific clock structure + * mask: preserve or zero MMCM register bits + * by selecting 1 or 0 on desired specific mask positions + * bitset: set those bits in MMCM register which are 1 in bitset + * + */ +static int litex_clk_set_divreg(struct clk_hw *clk_hw, u16 mask, u16 bitset) +{ + int ret; + + ret = litex_clk_change_value(clk_hw, mask, bitset, DIV_REG); + if (ret != 0) + return ret; + pr_debug("Global divider set to %d", (bitset % (1 << HIGH_TIME_POS)) + + ((bitset >> HIGH_TIME_POS) % (1 << HIGH_TIME_POS))); + return 0; +} + +/* + * Set global multiplier for all CLKOUTs + * + * clk_hw: hardware specific clock structure + * mask: preserve or zero MMCM register bits + * by selecting 1 or 0 on desired specific mask positions + * bitset: set those bits in MMCM register which are 1 in bitset + * + */ +static int litex_clk_set_mulreg(struct clk_hw *clk_hw, u16 mask, u16 bitset) +{ + int ret; + + ret = litex_clk_change_value(clk_hw, mask, bitset, CLKFBOUT_REG1); + if (ret != 0) + return ret; + pr_debug("Global multiplier set to %d", + (bitset % (1 << HIGH_TIME_POS)) + ((bitset >> HIGH_TIME_POS) % + (1 << HIGH_TIME_POS))); + return 0; +} + +static int litex_clk_set_filt(struct clk_hw *hw) +{ + u32 filt, mul; + int ret; + + mul = GLOB_MUL_VAL && HL_TIME_MASK; + mul += (GLOB_MUL_VAL >> HIGH_TIME_POS) && HL_TIME_MASK; + + filt = litex_clk_lookup_filter(mul - 1); + + ret = litex_clk_change_value(hw, FILT_MASK, filt >> 16, FILT_REG1); + if (ret != 0) + return ret; + ret = litex_clk_change_value(hw, FILT_MASK, filt, FILT_REG2); + if (ret != 0) + return ret; + return 0; +} + +static int litex_clk_set_lock(struct clk_hw *hw) +{ + u32 lock, mul; + int ret; + + mul = GLOB_MUL_VAL && HL_TIME_MASK; + mul += (GLOB_MUL_VAL >> HIGH_TIME_POS) && HL_TIME_MASK; + + lock = litex_clk_lookup_lock(mul - 1); + + ret = litex_clk_change_value(hw, LOCK1_MASK, lock & 0x3ff, LOCK_REG1); + if (ret != 0) + return ret; + ret = litex_clk_change_value(hw, LOCK23_MASK, + (((lock >> 16) & 0x1f) << 10) | 0x1, LOCK_REG2); + if (ret != 0) + return ret; + ret = litex_clk_change_value(hw, LOCK23_MASK, + (((lock >> 24) & 0x1f) << 10) | 0x3e9, LOCK_REG3); + if (ret != 0) + return ret; + return 0; +} + +/* + * Load default global divider and multiplier values + * + */ +static int litex_clk_set_dm(struct clk_hw *clk_hw) +{ + int ret; + + ret = litex_clk_set_divreg(clk_hw, KEEP_IN_DIV, GLOB_DIV_VAL); + if (ret != 0) + return ret; + ret = litex_clk_set_mulreg(clk_hw, KEEP_IN_MUL, GLOB_MUL_VAL); + if (ret != 0) + return ret; + ret = litex_clk_set_filt(clk_hw); + if (ret != 0) + return ret; + ret = litex_clk_set_lock(clk_hw); + if (ret != 0) + return ret; + return 0; +} + +/* Round scaled value*/ +static inline u32 litex_round(u32 val, u32 mod) +{ + if (val % mod > mod / 2) + return val / mod + 1; + else + return val / mod; +} + +/* + * Duty Cycle + */ + +/* + * Calculate necessary values for setting duty cycle in fractional mode + * + * divider: frequency divider value for calculating the phase + * high_duty: desired duty cycle in percent + * fract_cnt: fractional frequency divider value, if not for + * CLKOUT0 - set 0 + * pointers: function return values, used for setting desired duty + * + * This function is based on Xilinx Unified Simulation Library Component + * Advanced Mixed Mode Clock Manager (MMCM) taken from Xilinx Vivado + * found in: /data/verilog/src/unisims/MMCME2_ADV.v + * + */ +static int litex_clk_calc_duty_fract(int divider, int high_duty, int fract_cnt, + u8 *edge, u8 *high_time, u8 *low_time, + u8 *frac_wf_r, u8 *frac_wf_f) +{ + int even_part_high, even_part_low, odd, odd_and_frac; + + /* divider / 2 */ + even_part_high = divider >> 1; + even_part_low = even_part_high; + odd = divider - even_part_high - even_part_low; + odd_and_frac = (8 * odd) + fract_cnt; + + *low_time = even_part_high; + if (odd_and_frac <= ODD_AND_FRAC) + (*low_time)--; + + *high_time = even_part_low; + if (odd_and_frac <= EVEN_AND_FRAC) + (*high_time)--; + + *edge = divider % 2; + + if (((odd_and_frac >= 2) && (odd_and_frac <= ODD_AND_FRAC)) || + ((fract_cnt == 1) && (divider == 2))) + *frac_wf_f = 1; + else + *frac_wf_f = 0; + + if ((odd_and_frac >= 1) && (odd_and_frac <= EVEN_AND_FRAC)) + *frac_wf_r = 1; + else + *frac_wf_r = 0; + + return 0; +} +/* End of Vivado based code */ + +/* + * Calculate necessary values for setting duty cycle in normal mode + * + * divider: frequency divider value for calculating the phase + * high_duty: desired duty cycle in percent + * pointers: function return values, used for setting desired duty + * + */ +static int litex_clk_calc_duty_normal(int divider, int high_duty, u8 *edge, + u8 *high_time, u8 *low_time, + u8 *frac_wf_r, u8 *frac_wf_f) +{ + int synthetic_duty, delta_d, min_d; + u32 high_time_it, ht_aprox, edge_it; + + min_d = INT_MAX; + /* check if duty is available to set */ + ht_aprox = high_duty * divider; + if (ht_aprox > ((HIGH_LOW_TIME_REG_MAX * 100) + 50) || + (divider * 100) - ht_aprox > + ((HIGH_LOW_TIME_REG_MAX * 100) + 50)) + return -EINVAL; + + /* to prevent high_time == 0 or low_time == 0 */ + for (high_time_it = 1; high_time_it < divider; high_time_it++) { + for (edge_it = 0; edge_it < 2; edge_it++) { + synthetic_duty = (high_time_it * 100 + 50 * edge_it) / + divider; + delta_d = abs(synthetic_duty - high_duty); + /* check if low_time won't be above acceptable range */ + if (delta_d < min_d && (divider - high_time_it) <= + HIGH_LOW_TIME_REG_MAX) { + min_d = delta_d; + *high_time = high_time_it; + *low_time = divider - *high_time; + *edge = edge_it; + } + } + } + /* + * Calculating values in normal mode, + * clear control bits of fractional part + */ + + *frac_wf_f = 0; + *frac_wf_r = 0; + + return 0; +} + +/* Calculates duty cycle for given ratio in percent, 1% accuracy */ +static inline int litex_clk_calc_duty_percent(struct clk_hw *hw, + struct clk_duty *duty) +{ + u32 div, duty_ratio, ht; + + ht = duty->num; + div = duty->den; + duty_ratio = ht * 10000 / div; + + return litex_round(duty_ratio, 100); +} + +/* Calculates duty high_time for given divider and ratio */ +static inline int litex_clk_calc_duty_high_time(struct clk_hw *hw, + struct clk_duty *duty, + u32 divider) +{ + u32 high_duty; + + high_duty = litex_clk_calc_duty_percent(hw, duty) * divider; + + return litex_round(high_duty, 100); +} + +/* Returns accurate duty ratio of given clkout*/ +int litex_clk_get_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + int ret; + u32 divider; + u16 clkout_reg1, clkout_reg2; + u8 clkout_nr, high_time, edge, no_cnt, frac_en, frac_cnt; + + clkout_nr = l_clkout->id; + /* Check if divider is off */ + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg2, + &clkout_reg2); + if (ret != 0) + return ret; + edge = (clkout_reg2 >> EDGE_POS) & EDGE_MASK; + no_cnt = (clkout_reg2 >> NO_CNT_POS) & NO_CNT_MASK; + frac_en = (clkout_reg2 >> FRAC_EN_POS) & FRAC_EN_MASK; + frac_cnt = (clkout_reg2 >> FRAC_POS) & FRAC_MASK; + + /* get duty 50% when divider is off or fractional is enabled */ + if (no_cnt || (frac_en && frac_cnt)) { + duty->num = 1; + duty->den = 2; + return 0; + } + + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg1, + &clkout_reg1); + if (ret != 0) + return ret; + divider = clkout_reg1 & HL_TIME_MASK; + high_time = (clkout_reg1 >> HIGH_TIME_POS) & HL_TIME_MASK; + divider += high_time; + + /* Scaling to consider edge control bit */ + duty->num = high_time * 10 + edge * 5; + duty->den = (divider + edge) * 10; + + return 0; +} + +/* Set duty cycle with given ratio */ +int litex_clk_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u32 divider, fract_cnt, high_duty; + u16 bitset1, bitset2; + u8 frac_wf_r, frac_wf_f, frac_en, no_cnt, + edge, high_time, low_time, clkout_nr; + + clkout_nr = l_clkout->id; + edge = 0, high_time = 0, low_time = 0, frac_en = 0, + no_cnt = 0, frac_wf_r = 0, frac_wf_f = 0; + high_duty = litex_clk_calc_duty_percent(hw, duty); + + litex_clk_get_clkout_divider(hw, ÷r, &fract_cnt); + + if (fract_cnt == 0) { + int ret; + + ret = litex_clk_calc_duty_normal(divider, high_duty, &edge, + &high_time, &low_time, + &frac_wf_r, &frac_wf_f); + if (ret != 0) { + pr_err("CLKOUT%d: cannot set %d%% duty cycle for that frequency", + clkout_nr, high_duty); + return ret; + } + } else if (high_duty == 50) { + return 0; + } else { + pr_err("CLKOUT%d: cannot set duty cycle when fractional divider enabled!", + clkout_nr); + return -EACCES; + } + + bitset1 = (high_time << HIGH_TIME_POS) | low_time; + bitset2 = (edge << EDGE_POS); + + pr_debug("SET DUTY CYCLE: divider:%d fract_cnt:%d frac_wf_r:%d frac_wf_f:%d frac_en:%d no_cnt:%d edge:%d high_time:%d low_time:%d bitset1: 0x%x bitset2: 0x%x", + divider, fract_cnt, frac_wf_r, frac_wf_f, frac_en, + no_cnt, edge, high_time, low_time, bitset1, bitset2); + + return litex_clk_set_clock(hw, clkout_nr, REG1_DUTY_MASK, bitset1, + REG2_DUTY_MASK, bitset2); +} + +/* + * Phase + */ + +/* + * Calculate necessary values for setting phase in fractional mode + * + * clk_hw: hardware specific clock structure + * divider: frequency divider value for calculating the phase + * fract_cnt: fractional frequency divider value, if not for + * CLKOUT0 - set 0 + * phase_offset: desired phase in angle degrees + * pointers: function return values, used for setting desired phase + * + * This function is based on Xilinx Unified Simulation Library Component + * Advanced Mixed Mode Clock Manager (MMCM) taken from Xilinx Vivado + * found in: /data/verilog/src/unisims/MMCME2_ADV.v + * + */ +static int litex_clk_calc_phase_fract(struct clk_hw *hw, u32 divider, + u32 fract_cnt, u32 phase_offset, + u32 *phase_mux, u32 *delay_time, + u32 *phase_mux_f, u32 *period_offset) +{ + int phase, dt_calc, dt, + a_per_in_octets, a_phase_in_cycles, + pm_rise_frac, pm_rise_frac_filtered, + pm_fall, pm_fall_frac, pm_fall_frac_filtered; + + phase = phase_offset * 1000; + a_per_in_octets = (8 * divider) + fract_cnt; + a_phase_in_cycles = (phase + 10) * a_per_in_octets / 360000; + + if ((a_phase_in_cycles & FULL_BYTE) != 0) + pm_rise_frac = (a_phase_in_cycles & PHASE_MUX_MAX); + else + pm_rise_frac = 0; + + dt_calc = a_phase_in_cycles / 8; + dt = dt_calc & FULL_BYTE; + + pm_rise_frac_filtered = pm_rise_frac; + if (pm_rise_frac >= 8) + pm_rise_frac_filtered -= 8; + + pm_fall = ((divider % 2) << 2) + ((fract_cnt >> 1) & TWO_LSBITS); + pm_fall_frac = pm_fall + pm_rise_frac; + pm_fall_frac_filtered = pm_fall + pm_rise_frac - + (pm_fall_frac & F_FRAC_MASK); + + /* keep only the lowest bits to fit in control registers bit groups */ + *delay_time = dt % (1 << 6); + *phase_mux = pm_rise_frac_filtered % (1 << 3); + *phase_mux_f = pm_fall_frac_filtered % (1 << 3); + + return 0; +} +/* End of Vivado based code */ + +/* + * Calculate necessary values for setting phase in normal mode + * + * clk_hw: hardware specific clock structure + * divider: frequency divider value for calculating the phase + * fract_cnt: fractional frequency divider value, if not for + * CLKOUT0 - set 0 + * phase_offset: desired phase in angle degrees + * pointers: function return values, used for setting desired phase + * + */ +static int litex_clk_calc_phase_normal(struct clk_hw *hw, u32 divider, + u32 fract_cnt, u32 phase_offset, + u32 *phase_mux, u32 *delay_time, + u32 *phase_mux_f, u32 *period_offset) +{ + /* ns unit */ + u32 global_period, clkout_period, post_glob_div_f; + + post_glob_div_f = litex_clk_get_real_global_frequency(hw); + global_period = NS_IN_SEC / post_glob_div_f; + clkout_period = global_period * divider; + + if (phase_offset != 0) { + int synthetic_phase, delta_p, min_p; + u8 d_t, p_m; + + *period_offset = litex_round(clkout_period * (*period_offset), + 10000); + + if (*period_offset / global_period > DELAY_TIME_MAX) + return -EINVAL; + + min_p = INT_MAX; + /* Delay_time: (0-63) */ + for (d_t = 0; d_t <= DELAY_TIME_MAX; d_t++) { + /* phase_mux: (0-7) */ + for (p_m = 0; p_m <= PHASE_MUX_MAX; p_m++) { + synthetic_phase = (d_t * global_period) + + ((p_m * ((global_period * 100) / 8) / 100)); + + delta_p = abs(synthetic_phase - *period_offset); + if (delta_p < min_p) { + min_p = delta_p; + *phase_mux = p_m; + *delay_time = d_t; + } + } + } + } else { + /* Don't change phase offset*/ + *phase_mux = 0; + *delay_time = 0; + } +/* Calculating values in normal mode, fractional control bits need to be zero*/ + *phase_mux_f = 0; + + return 0; +} + +/* + * Convert phase offset to positive lower than 360 deg. + * and calculate period in nanoseconds + * + */ +static int litex_clk_prepare_phase(int *phase_offset, u32 *period_offset) +{ + *phase_offset = *phase_offset % 360; + + if (*phase_offset < 0) + *phase_offset += 360; + + *period_offset = ((*phase_offset * 10000) / 360); + + return 0; +} + +/* + * Calculate necessary values for setting phase + * + * clk_hw: hardware specific clock structure + * divider: frequency divider value for calculating the phase + * fract_cnt: fractional frequency divider value, if not for + * CLKOUT0 - set 0 + * clkout_nr: clock output number + * phase_offset: desired phase in angle degrees + * pointers: function return values, used for setting desired phase + * + */ +static int litex_clk_calc_phase(struct clk_hw *clk_hw, + u32 divider, u32 fract_cnt, + u32 clkout_nr, int phase_offset, + u32 *phase_mux, u32 *delay_time, + u32 *phase_mux_f, u32 *period_offset) +{ + int ret; + + litex_clk_prepare_phase(&phase_offset, period_offset); + + if (clkout_nr == 0 && fract_cnt > 0) + ret = litex_clk_calc_phase_fract(clk_hw, divider, fract_cnt, + phase_offset, + phase_mux, delay_time, + phase_mux_f, period_offset); + else + ret = litex_clk_calc_phase_normal(clk_hw, divider, fract_cnt, + phase_offset, + phase_mux, delay_time, + phase_mux_f, period_offset); + return ret; +} + +/* Returns phase-specific values of given clock output */ +static int litex_clk_get_phase_data(struct clk_hw *hw, u8 *phase_mux, + u8 *delay_time, u8 *phase_mux_f) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + int ret; + u16 r1, r2; + u8 clkout_nr = l_clkout->id; + + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg1, &r1); + if (ret != 0) + return ret; + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg2, &r2); + if (ret != 0) + return ret; + *phase_mux = (r1 >> PHASE_MUX_POS) & PHASE_MUX_MASK; + *delay_time = (r2 >> DELAY_TIME_POS) & HL_TIME_MASK; + + if (clkout_nr == 0) { + ret = litex_clk_get_DO(hw, CLKOUT5_REG2, &r2); + if (ret != 0) + return ret; + *phase_mux_f = (r2 >> PHASE_MUX_F_POS) & PHASE_MUX_F_MASK; + } else { + *phase_mux_f = 0; + } + + return 0; +} + +/* Returns phase of given clock output in degrees */ +int litex_clk_get_phase(struct clk_hw *hw) +{ + u8 phase_mux = 0, delay_time = 0, phase_mux_f; + u32 divider, fract_cnt, post_glob_div_f, pm; + /* ns unit */ + u32 global_period, clkout_period, period; + + litex_clk_get_phase_data(hw, &phase_mux, &delay_time, &phase_mux_f); + litex_clk_get_clkout_divider(hw, ÷r, &fract_cnt); + + post_glob_div_f = litex_clk_get_real_global_frequency(hw); + global_period = NS_IN_SEC / post_glob_div_f; + clkout_period = global_period * divider; + + pm = (phase_mux * global_period * 1000) / PHASE_MUX_RES_FACTOR; + pm = litex_round(pm, 1000); + + period = delay_time * global_period + pm; + + period = period * 1000 / clkout_period; + period = period * 360; + + return litex_round(period, 1000); +} + +/* Sets phase given in degrees on given clock output */ +int litex_clk_set_phase(struct clk_hw *hw, int degrees) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u32 phase_mux, divider, fract_cnt, + phase_mux_f, delay_time, period_offset; + u16 bitset1, bitset2, reg2_mask; + u8 clkout_nr, edge, high_time, low_time, frac_wf_r, frac_wf_f; + int ret; + + reg2_mask = REG2_PHASE_MASK; + phase_mux = 0; + delay_time = 0; + clkout_nr = l_clkout->id; + litex_clk_get_clkout_divider(hw, ÷r, &fract_cnt); + if (fract_cnt > 0 && degrees != 0) { + pr_err("CLKOUT%d: cannot set phase on that frequency", + clkout_nr); + return -ENOSYS; + } + ret = litex_clk_calc_phase(hw, divider, fract_cnt, clkout_nr, degrees, + &phase_mux, &delay_time, + &phase_mux_f, &period_offset); + if (ret != 0) { + pr_err("CLKOUT%d: phase offset %d deg is too high for given frequency", + clkout_nr, degrees); + return ret; + } + + bitset1 = (phase_mux << PHASE_MUX_POS); + bitset2 = (delay_time << DELAY_TIME_POS); + + if (clkout_nr == 0 && fract_cnt > 0) { + u16 clkout5_reg2_bitset; + + litex_clk_calc_duty_fract(divider, 50, fract_cnt, + &edge, &high_time, &low_time, + &frac_wf_r, &frac_wf_f); + bitset2 |= (fract_cnt << FRAC_POS) | + (1 << FRAC_EN_POS) | + (frac_wf_r << FRAC_WF_R_POS); + + clkout5_reg2_bitset = (phase_mux_f << PHASE_MUX_F_POS) | + (frac_wf_f << FRAC_WF_F_POS); + + ret = litex_clk_change_value(hw, CLKOUT5_FRAC_MASK, + clkout5_reg2_bitset, + CLKOUT5_REG2); + if (ret != 0) + return ret; + reg2_mask = REG2_PHASE_F_MASK; + } + + pr_debug("SET PHASE: phase_mux:%d phase_mux_f:%d delay_time:%d bitset1: 0x%x bitset2: 0x%x", + phase_mux, phase_mux_f, delay_time, bitset1, bitset2); + + return litex_clk_set_clock(hw, clkout_nr, REG1_PHASE_MASK, bitset1, + reg2_mask, bitset2); +} + +/* + * Frequency + */ + +/* + * Calculate necessary values for setting frequency in fractional mode + * + * clk_hw: hardware specific clock structure + * freq: desired frequency in Hz + * pointers: function return values, used for setting + * desired frequency + */ +static int litex_clk_calc_freq_fract(struct clk_hw *clk_hw, u32 freq, + u32 *divider, u8 *frac_en, + u8 *no_cnt, u32 *fract_cnt) +{ + int delta_f, synthetic_freq, min_f; + u32 post_glob_div_f, div, f_cnt, fract; + + post_glob_div_f = litex_clk_get_real_global_frequency(clk_hw); + + delta_f = 0; + synthetic_freq = post_glob_div_f; + min_f = abs(synthetic_freq - freq); + *divider = 1; + /* div is integer divider * 1000 to keep precision */ + for (div = MIN_DIVIDER_F; div <= MAX_DIVIDER_F; div += 1000) { + /* + * f_cnt is fractional part of divider * 1000 incremented + * by minimal fractional divider step to keep precision + * and find best divider + */ + for (f_cnt = 0, fract = 0; fract < 1000; + fract += FRACT_DIV_RES, f_cnt++) { + + synthetic_freq = (post_glob_div_f / (div + fract)) * + 1000; + delta_f = abs(synthetic_freq - freq); + + if (delta_f < min_f) { + min_f = delta_f; + *divider = div / 1000; + *fract_cnt = f_cnt; + } + } + } + if (*fract_cnt == 0) { + *frac_en = 0; + if (*divider == 1) + *no_cnt = 1; + else + *no_cnt = 0; + } else { + *frac_en = 1; + *no_cnt = 0; + } + + return 0; +} + +/* + * Calculate necessary values for setting frequency in normal mode + * + * clk_hw: hardware specific clock structure + * freq: desired frequency in Hz + * pointers: function return values, used for setting + * desired frequency + * + */ +static int litex_clk_calc_freq_normal(struct clk_hw *clk_hw, u32 freq, + u32 *divider, u8 *frac_en, + u8 *no_cnt, u32 *fract_cnt) +{ + int delta_f, synthetic_freq, min_f; + u32 post_glob_div_f, div; + + post_glob_div_f = litex_clk_get_real_global_frequency(clk_hw); + synthetic_freq = post_glob_div_f; + min_f = abs(synthetic_freq - freq); + *divider = 1; + delta_f = 0; + + for (div = MIN_DIVIDER; div <= MAX_DIVIDER; div++) { + synthetic_freq = post_glob_div_f / div; + delta_f = abs(synthetic_freq - freq); + if (delta_f < min_f) { + min_f = delta_f; + *divider = div; + } + } + if (*divider == 1) + *no_cnt = 1; + else + *no_cnt = 0; + + *frac_en = 0; + *fract_cnt = 0; + + return 0; +} + +/* Returns rate in Hz */ +static inline unsigned long litex_clk_calc_rate(struct clk_hw *hw, u32 g_mul, + u32 g_div, u32 div, + u32 fract_cnt) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + + return (l_clkout->sys_clk_freq * g_mul / + ((div * 1000 + (fract_cnt * FRACT_DIV_RES)) * g_div)) * 1000; +} + +/* + * Calculate necessary values for setting frequency + * + * clk_hw: hardware specific clock structure + * rate: desired frequency in Hz + * clkout_nr: clock output number + * pointers: function return values, used for setting + * desired frequency + * + */ +static void litex_clk_calc_dividers(struct clk_hw *hw, u32 rate, u8 clkout_nr, + u32 *divider, u32 *fract_cnt, + u8 *frac_wf_r, u8 *frac_wf_f, + u8 *frac_en, u8 *no_cnt, u8 *edge, + u8 *high_time, u8 *low_time) +{ + u32 high_duty; + struct clk_duty duty; + + litex_clk_get_duty_cycle(hw, &duty); + high_duty = litex_clk_calc_duty_percent(hw, &duty); + + if (clkout_nr == 0) { + litex_clk_calc_freq_fract(hw, rate, divider, frac_en, no_cnt, + fract_cnt); + if (*fract_cnt > 0) + litex_clk_calc_duty_fract(*divider, high_duty, + *fract_cnt, edge, + high_time, low_time, + frac_wf_r, frac_wf_f); + else + litex_clk_calc_duty_normal(*divider, high_duty, edge, + high_time, low_time, + frac_wf_r, frac_wf_f); + } else { + + litex_clk_calc_freq_normal(hw, rate, divider, frac_en, + no_cnt, fract_cnt); + + litex_clk_calc_duty_normal(*divider, high_duty, edge, + high_time, low_time, + frac_wf_r, frac_wf_f); + } +} + +/* Returns rate of given CLKOUT, parent_rate ignored */ +unsigned long litex_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + u32 multiplier, divider, clkout_div, fract_cnt; + + LITEX_UNUSED(parent_rate); + + litex_clk_get_global_divider(hw, ÷r, &multiplier); + litex_clk_get_clkout_divider(hw, &clkout_div, &fract_cnt); + return litex_clk_calc_rate(hw, multiplier, divider, clkout_div, + fract_cnt); +} + +int litex_clk_check_rate_range(struct clk_hw *hw, unsigned long rate) +{ + u32 f_max, f_min, gf; + + gf = litex_clk_get_real_global_frequency(hw); + f_max = gf / 2; + f_min = gf / MAX_DIVIDER; + /* margin of 1kHz */ + if ((rate > f_max + KHZ) | (rate < f_min - KHZ)) + return -EINVAL; + + return 0; +} + +/* Returns closest available clock rate in Hz, parent_rate ignored */ +long litex_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u32 divider, fract_cnt, g_divider, g_multiplier; + u8 frac_wf_r, frac_wf_f, frac_en, no_cnt, + edge, high_time, low_time, clkout_nr; + + if (litex_clk_check_rate_range(hw, rate)) + return -EINVAL; + + LITEX_UNUSED(parent_rate); + clkout_nr = l_clkout->id; + + litex_clk_calc_dividers(hw, rate, clkout_nr, ÷r, &fract_cnt, + &frac_wf_r, &frac_wf_f, &frac_en, + &no_cnt, &edge, &high_time, &low_time); + litex_clk_get_global_divider(hw, &g_divider, &g_multiplier); + + return litex_clk_calc_rate(hw, g_multiplier, g_divider, divider, + fract_cnt); +} + +/* Set closest available clock rate in Hz, parent_rate ignored */ +int litex_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u16 bitset1, bitset2; + u32 divider, fract_cnt; + u8 frac_wf_r, frac_wf_f, frac_en, no_cnt, + edge, high_time, low_time, clkout_nr; + int ret; + + LITEX_UNUSED(parent_rate); + clkout_nr = l_clkout->id; + + pr_debug("set_rate: %lu", rate); + if (litex_clk_check_rate_range(hw, rate)) { + pr_err("CLKOUT%d: cannot set %luHz, frequency not in available range", + clkout_nr, rate); + return -EINVAL; + } + + litex_clk_calc_dividers(hw, rate, clkout_nr, ÷r, &fract_cnt, + &frac_wf_r, &frac_wf_f, &frac_en, &no_cnt, + &edge, &high_time, &low_time); + + bitset1 = (high_time << HIGH_TIME_POS) | + (low_time << LOW_TIME_POS); + + bitset2 = (fract_cnt << FRAC_POS) | + (frac_en << FRAC_EN_POS) | + (frac_wf_r << FRAC_WF_R_POS) | + (edge << EDGE_POS) | + (no_cnt << NO_CNT_POS); + if (frac_en != 0) { + u32 phase_mux, delay_time, phase_mux_f, + phase_offset, period_offset; + + phase_offset = 0; + litex_clk_calc_phase_fract(hw, divider, fract_cnt, phase_offset, + &phase_mux, &delay_time, + &phase_mux_f, &period_offset); + + bitset1 |= (phase_mux << PHASE_MUX_POS); + bitset2 |= (delay_time << DELAY_TIME_POS); + + if (frac_wf_f > 0 || phase_mux_f > 0) { + u16 clkout5_reg2_bitset = 0; + + clkout5_reg2_bitset = (phase_mux_f << PHASE_MUX_F_POS) | + (frac_wf_f << FRAC_WF_F_POS); + + ret = litex_clk_change_value(hw, CLKOUT5_FRAC_MASK, + clkout5_reg2_bitset, CLKOUT5_REG2); + if (ret != 0) + return ret; + } + } + + pr_debug("SET RATE: divider:%d fract_cnt:%d frac_wf_r:%d frac_wf_f:%d frac_en:%d no_cnt:%d edge:%d high_time:%d low_time:%d bitset1: 0x%x bitset2: 0x%x", + divider, fract_cnt, frac_wf_r, frac_wf_f, frac_en, + no_cnt, edge, high_time, low_time, bitset1, bitset2); + + return litex_clk_set_clock(hw, clkout_nr, REG1_FREQ_MASK, bitset1, + REG2_FREQ_MASK, bitset2); +} + +/* Prepare clock output */ +int litex_clk_prepare(struct clk_hw *hw) +{ + struct litex_clk_clkout *l_clkout; + struct clk_duty duty; + u32 freq, phase; + + l_clkout = clk_hw_to_litex_clk_clkout(hw); + freq = l_clkout->default_freq; + phase = l_clkout->default_phase; + duty.num = l_clkout->default_duty_num; + duty.den = l_clkout->default_duty_den; + + litex_clk_set_rate(hw, freq, 0); + litex_clk_set_duty_cycle(hw, &duty); + litex_clk_set_phase(hw, phase); + return 0; +} + +/* Unrepare clock output */ +void litex_clk_unprepare(struct clk_hw *hw) +{ + struct clk_duty duty; + + duty.num = 1; + duty.den = 2; + litex_clk_set_rate(hw, 100000000, 0); + litex_clk_set_duty_cycle(hw, &duty); + litex_clk_set_phase(hw, 0); +} + +/* Enable Clock Control MMCM module */ +int litex_clk_enable(struct clk_hw *hw) +{ + litex_clk_assert_reg(hw, DRP_RESET); + return 0; +} + +/* Disable Clock Control MMCM module */ +void litex_clk_disable(struct clk_hw *hw) +{ + litex_clk_deassert_reg(hw, DRP_RESET); +} + +/* Set default clock value from device tree for given clkout*/ +static int litex_clk_set_def_clkout(struct clk_hw *hw, u32 freq, u32 phase, + struct clk_duty *duty) +{ + int ret; + + ret = litex_clk_set_rate(hw, freq, 0); + if (ret != 0) + return ret; + ret = litex_clk_set_duty_cycle(hw, duty); + if (ret != 0) + return ret; + ret = litex_clk_set_phase(hw, phase); + if (ret != 0) + return ret; + return 0; +} + +static const struct clk_ops litex_clk_ops = { + .prepare = litex_clk_prepare, + .unprepare = litex_clk_unprepare, + .enable = litex_clk_enable, + .disable = litex_clk_disable, + .recalc_rate = litex_clk_recalc_rate, + .round_rate = litex_clk_round_rate, + .set_rate = litex_clk_set_rate, + .get_phase = litex_clk_get_phase, + .set_phase = litex_clk_set_phase, + .get_duty_cycle = litex_clk_get_duty_cycle, + .set_duty_cycle = litex_clk_set_duty_cycle, +}; + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,clk"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static int litex_clk_read_clkout_dts(struct device_node *child, + struct litex_clk_clkout *clkout) +{ + int ret; + u32 dt_num, dt_freq, dt_phase, dt_duty_num, + dt_duty_den, f_max, f_min, gf; + + gf = litex_clk_get_expctd_global_frequency(clkout->sys_clk_freq); + f_max = gf / 2; + f_min = gf / MAX_DIVIDER; + + ret = of_property_read_u32(child, "reg", &dt_num); + if (ret != 0) + return -ret; + + if (dt_num > CLKOUT_MAX) { + pr_err("%s: Invalid CLKOUT index!", child->name); + return -EINVAL; + } + + ret = of_property_read_u32(child, "litex,clock-frequency", &dt_freq); + if (ret != 0) + return -ret; + + if (dt_freq > f_max || dt_freq < f_min) { + pr_err("%s: Invalid default frequency!(%d), MIN/MAX (%d/%d)Hz", + child->name, dt_freq, f_min, f_max); + return -EINVAL; + } + + ret = of_property_read_u32(child, "litex,clock-phase", &dt_phase); + if (ret != 0) + return -ret; + + ret = of_property_read_u32(child, "litex,clock-duty-den", &dt_duty_den); + if (ret != 0) + return -ret; + + ret = of_property_read_u32(child, "litex,clock-duty-num", &dt_duty_num); + if (ret != 0) + return -ret; + + if (dt_duty_den <= 0 || dt_duty_num > dt_duty_den) { + pr_err("%s: Invalid default duty! %d/%d", child->name, + dt_duty_num, dt_duty_den); + return -EINVAL; + } + + clkout->id = dt_num; + clkout->default_freq = dt_freq; + clkout->default_phase = dt_phase; + clkout->default_duty_num = dt_duty_num; + clkout->default_duty_den = dt_duty_den; + + return 0; +} + +static int litex_clk_get_timeouts(struct device_node *node, int *dt_lock_timeout, + int *dt_drdy_timeout) +{ + int ret; + + /* Read wait_lock timeout from device property*/ + ret = of_property_read_u32(node, "litex,lock-timeout", dt_lock_timeout); + if (ret < 0) { + pr_err("No litex,lock_timeout entry in the dts file\n"); + return -ENODEV; + } + if (*dt_lock_timeout < 1) { + pr_err("LiteX CLK driver cannot wait for time bellow ca. 1ms\n"); + return -EINVAL; + } + + /* Read wait_drdy timeout from device property*/ + ret = of_property_read_u32(node, "litex,drdy-timeout", dt_drdy_timeout); + if (ret < 0) { + pr_err("No litex,lock_drdy entry in the dts file\n"); + return -ENODEV; + } + if (*dt_drdy_timeout < 1) { + pr_err("LiteX CLK driver cannot wait for time bellow ca. 1ms\n"); + return -EINVAL; + } + + return 0; +} + +static int litex_clk_init_clkouts(struct device *dev, + struct device_node *node, + struct litex_clk_device *l_dev) +{ + struct device_node *child_node; + struct clk_hw *hw; + struct clk_duty duty; + u32 dt_lock_timeout, dt_drdy_timeout; + int i = 0, ret; + + ret = litex_clk_get_timeouts(node, &dt_lock_timeout, &dt_drdy_timeout); + if (ret != 0) + return ret; + + for_each_child_of_node(node, child_node) { + struct litex_clk_clkout *clkout; + struct clk_init_data clkout_init; + + clkout = &l_dev->clkouts[i]; + clkout->is_clkout = true; + + clkout->lock_timeout = dt_lock_timeout; + clkout->drdy_timeout = dt_drdy_timeout; + clkout->sys_clk_freq = l_dev->sys_clk_freq; + + ret = litex_clk_read_clkout_dts(child_node, clkout); + if (ret != 0) + return ret; + + clkout_init.name = child_node->name; + clkout_init.ops = &litex_clk_ops; + clkout_init.num_parents = 0; + clkout_init.flags = CLK_SET_RATE_UNGATE | CLK_GET_RATE_NOCACHE; + clkout->clk_hw.init = &clkout_init; + clkout->base = l_dev->base; + + duty.num = clkout->default_duty_num; + duty.den = clkout->default_duty_den; + + /* set global divider and multiplier once */ + if (i == 0) { + hw = &l_dev->clkouts[i].clk_hw; + litex_clk_enable(hw); + ret = litex_clk_set_dm(hw); + litex_clk_disable(hw); + if (ret != 0) + return ret; + } + + ret = litex_clk_set_def_clkout(&clkout->clk_hw, + clkout->default_freq, + clkout->default_phase, &duty); + ret = devm_clk_hw_register(dev, &clkout->clk_hw); + if (ret != 0) { + pr_err("devm_clk_hw_register failure, %s ret: %d\n", + child_node->name, ret); + return ret; + } + + ret = of_clk_add_hw_provider(child_node, of_clk_hw_simple_get, + &clkout->clk_hw); + if (ret != 0) + return ret; + i++; + } + return 0; +} + +static int litex_clk_read_global_dts(struct device *dev, + struct device_node *node, + struct litex_clk_device *l_dev) +{ + struct device_node *child_node; + struct litex_clk_clkout *l_clkout; + u32 dt_nclkout, dt_lock_timeout, dt_drdy_timeout, dt_sys_clk_freq; + int i = 0, ret; + + ret = of_property_read_u32(node, "litex,sys-clock-frequency", + &dt_sys_clk_freq); + if (ret < 0) { + pr_err("No clock-frequency entry in the dts file\n"); + return -ENODEV; + } + l_dev->sys_clk_freq = dt_sys_clk_freq; + + /* Read cnt of CLKOUTs from device property*/ + ret = of_property_read_u32(node, "litex,nclkout", &dt_nclkout); + if (ret < 0) { + pr_err("No litex,nclkout entry in the dts file\n"); + return -ENODEV; + } + if (dt_nclkout > CLKOUT_MAX) { + pr_err("LiteX CLK driver cannot use more than %d clock outputs\n", + CLKOUT_MAX); + return -EINVAL; + } + l_dev->nclkout = dt_nclkout; + + /* count existing CLKOUTs */ + for_each_child_of_node(node, child_node) + i++; + if (i != dt_nclkout) { + pr_err("nclkout(%d) not matching actual CLKOUT count(%d)!", + dt_nclkout, i); + return -EINVAL; + } + + l_dev->clkouts = devm_kzalloc(dev, sizeof(*l_clkout) * i, GFP_KERNEL); + if (!l_dev->clkouts) { + pr_err("CLKOUT memory allocation failure!"); + return -ENOMEM; + } + + ret = litex_clk_get_timeouts(node, &dt_lock_timeout, &dt_drdy_timeout); + if (ret != 0) + return ret; + l_dev->lock_timeout = dt_lock_timeout; + l_dev->drdy_timeout = dt_drdy_timeout; + + return 0; +} + +static int litex_clk_init_glob_clk(struct device *dev, + struct device_node *node, + struct litex_clk_device *l_dev) +{ + struct clk_init_data init; + struct clk_hw *hw; + int ret; + + init.name = node->name; + init.ops = &litex_clk_ops; + init.flags = CLK_SET_RATE_UNGATE; + + hw = &l_dev->clk_hw; + hw->init = &init; + + ret = devm_clk_hw_register(dev, hw); + if (ret != 0) { + pr_err("devm_clk_hw_register failure, ret: %d\n", ret); + return ret; + } + + ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw); + if (ret != 0) { + pr_err("of_clk_add_hw_provider failure, ret: %d", ret); + return ret; + } + + /* Power on MMCM module */ + ret = litex_clk_change_value(hw, FULL_REG_16, FULL_REG_16, POWER_REG); + if (ret != 0) { + pr_err("MMCM initialization failure, ret: %d", ret); + return ret; + } + + return 0; +} + +static int litex_clk_probe(struct platform_device *pdev) +{ + const struct of_device_id *id; + struct device *dev; + struct device_node *node; + struct litex_clk_device *litex_clk_device; + struct resource *res; + int ret; + + dev = &pdev->dev; + node = dev->of_node; + if (!node) + return -ENODEV; + + id = of_match_node(litex_of_match, node); + if (!id) + return -ENODEV; + + litex_clk_device = devm_kzalloc(dev, sizeof(*litex_clk_device), + GFP_KERNEL); + if (IS_ERR_OR_NULL(litex_clk_device)) + return -ENOMEM; + litex_clk_device->is_clkout = false; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR_OR_NULL(res)) + return -EBUSY; + + litex_clk_device->base = devm_of_iomap(dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(litex_clk_device->base)) + return -EIO; + + ret = litex_clk_read_global_dts(dev, node, litex_clk_device); + if (ret != 0) + return ret; + + ret = litex_clk_init_clkouts(dev, node, litex_clk_device); + if (ret != 0) + return ret; + + ret = litex_clk_init_glob_clk(dev, node, litex_clk_device); + if (ret != 0) + return ret; + + pr_info("litex clk control driver initialized"); + return 0; +} + +static int litex_clk_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + return 0; +} + +static struct platform_driver litex_clk_driver = { + .driver = { + .name = "litex-clk", + .of_match_table = of_match_ptr(litex_of_match), + }, + .probe = litex_clk_probe, + .remove = litex_clk_remove, +}; + +module_platform_driver(litex_clk_driver); +MODULE_DESCRIPTION("LiteX clock driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/clk-litex.h b/drivers/clk/clk-litex.h new file mode 100644 index 00000000000000..76be275d581a6e --- /dev/null +++ b/drivers/clk/clk-litex.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#define LITEX_UNUSED(var) (void)(var) + +/* Common values */ +#define NS_IN_SEC 1000000000 +#define FULL_BYTE 0xFF +#define KHZ 1000 + +/* MMCM specific numbers */ +#define CLKOUT_MAX 7 +#define DELAY_TIME_MAX 63 +#define PHASE_MUX_MAX 7 +#define HIGH_LOW_TIME_SAFE_MAX 62 +#define HIGH_LOW_TIME_REG_MAX 63 +#define MAX_DIVIDER 126 +#define MAX_DIVIDER_F 126000 +#define MIN_DIVIDER 2 +#define MIN_DIVIDER_F 2000 +#define PHASE_MUX_RES_FACTOR 8 +/* Fractional divider resolution multiplied by 1000 to keep precision */ +#define FRACT_DIV_RES 125 +/* Minimal value when div is odd and fract is on */ +#define ODD_AND_FRAC 9 +/* Odd div and no frac or even div and enabled fract max value */ +#define EVEN_AND_FRAC 8 + +/* DRP registers index */ +/* Additional register */ +#define MMCM 0 +/* DRP registers */ +#define DRP_RESET 0 +#define DRP_READ 1 +#define DRP_WRITE 2 +#define DRP_DRDY 3 +#define DRP_ADR 4 +#define DRP_DAT_W 5 +#define DRP_DAT_R 6 +#define DRP_LOCKED 7 + +/* Register space offsets */ +#define DRP_OF_RESET 0x0 +#define DRP_OF_LOCKED 0x4 +#define DRP_OF_READ 0x8 +#define DRP_OF_WRITE 0xc +#define DRP_OF_DRDY 0x10 +#define DRP_OF_ADR 0x14 +#define DRP_OF_DAT_W 0x18 +#define DRP_OF_DAT_R 0x20 + +/* Register sizes */ +#define DRP_SIZE_RESET 0x1 +#define DRP_SIZE_READ 0x1 +#define DRP_SIZE_WRITE 0x1 +#define DRP_SIZE_DRDY 0x1 +#define DRP_SIZE_ADR 0x1 +#define DRP_SIZE_DAT_W 0x2 +#define DRP_SIZE_DAT_R 0x2 +#define DRP_SIZE_LOCKED 0x1 + +/* Register values */ +#define GLOB_DIV_VAL 0x41 +#define GLOB_MUL_VAL 0x82 +#define FULL_REG_16 0xFFFF +#define KEEP_REG_16 FULL_REG_16 +#define KEEP_IN_MUL 0xF000 +#define KEEP_IN_DIV 0xE000 +#define ZERO_REG 0x0 +#define REG1_MASK 0x1000 +#define REG2_MASK 0x8000 +#define REG1_BITSET 0x41 +#define REG2_BITSET 0x40 +#define CLKOUT5_FRAC_MASK 0xC3FF +#define CLKOUT5_FRAC_MASK_F 0xFBFF +#define CLKOUT5_FRAC_MASK_P 0xC7FF +#define REG1_FREQ_MASK 0xF000 +#define REG2_FREQ_MASK 0x803F +#define REG1_DUTY_MASK 0xF000 +#define REG2_DUTY_MASK 0xFF7F +#define REG1_PHASE_MASK 0x1FFF +#define REG2_PHASE_MASK 0xFCC0 +#define REG2_PHASE_F_MASK 0x80C0 +#define FILT_MASK 0x9900 +#define LOCK1_MASK 0x03FF +#define LOCK23_MASK 0x7FFF +/* Control bits extraction masks */ +#define HL_TIME_MASK 0x3F +#define FRAC_MASK 0x7 +#define EDGE_MASK 0x1 +#define NO_CNT_MASK 0x1 +#define FRAC_EN_MASK 0x1 +#define PHASE_MUX_MASK 0x7 +#define PHASE_MUX_F_MASK 0x7 +#define F_FRAC_MASK 0xF8 +#define TWO_LSBITS 0x3 + +/* Bit groups start position in DRP registers */ +#define HIGH_TIME_POS 6 +#define LOW_TIME_POS 0 +#define PHASE_MUX_POS 13 +#define PHASE_MUX_F_POS 11 +#define FRAC_POS 12 +#define FRAC_EN_POS 11 +#define FRAC_WF_R_POS 10 +#define FRAC_WF_F_POS 10 +#define EDGE_POS 7 +#define NO_CNT_POS 6 +#define DELAY_TIME_POS 0 + +/* MMCM Register addresses */ +#define POWER_REG 0x28 +#define DIV_REG 0x16 +#define LOCK_REG1 0x18 +#define LOCK_REG2 0x19 +#define LOCK_REG3 0x1A +#define FILT_REG1 0x4E +#define FILT_REG2 0x4F +#define CLKOUT0_REG1 0x08 +#define CLKOUT0_REG2 0x09 +#define CLKOUT1_REG1 0x0A +#define CLKOUT1_REG2 0x0B +#define CLKOUT2_REG1 0x0C +#define CLKOUT2_REG2 0x0D +#define CLKOUT3_REG1 0x0E +#define CLKOUT3_REG2 0x0F +#define CLKOUT4_REG1 0x10 +#define CLKOUT4_REG2 0x11 +#define CLKOUT5_REG1 0x06 +#define CLKOUT5_REG2 0x07 +#define CLKOUT6_REG1 0x12 +#define CLKOUT6_REG2 0x13 +#define CLKFBOUT_REG1 0x14 +#define CLKFBOUT_REG2 0x15 From 4869c30f03a448f341f7a62497396a2f9c8f2970 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Sun, 27 Dec 2020 16:16:19 -0500 Subject: [PATCH 22/31] fixup over ("LiteX: driver for MMCM") FIXME: if possible, replace calls to '_litex_[get|set]_reg()' with the appropriate 'litex_[read|write][8|16|32|64]()'. If the size of a LiteX CSR access can't be determined at compile time, we should make available a set of public 'litex_[get|set]_reg()' methods that add 'BUG_ON(reg_size > sizeof(u64) || reg_size < 1)' on top of the call to '_litex_[get|set]_reg()'. --- drivers/clk/clk-litex.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/clk-litex.c b/drivers/clk/clk-litex.c index 196ea56392c893..2e1f89d795b5e0 100644 --- a/drivers/clk/clk-litex.c +++ b/drivers/clk/clk-litex.c @@ -167,14 +167,14 @@ static inline void litex_clk_set_reg(struct clk_hw *clk_hw, u32 reg, u32 val) { struct litex_clk_clkout *l_clk = clk_hw_to_litex_clk_clkout(clk_hw); - litex_set_reg(l_clk->base + drp[reg].offset, drp[reg].size, val); + _litex_set_reg(l_clk->base + drp[reg].offset, drp[reg].size, val); } static inline u32 litex_clk_get_reg(struct clk_hw *clk_hw, u32 reg) { struct litex_clk_clkout *l_clk = clk_hw_to_litex_clk_clkout(clk_hw); - return litex_get_reg(l_clk->base + drp[reg].offset, drp[reg].size); + return _litex_get_reg(l_clk->base + drp[reg].offset, drp[reg].size); } static inline void litex_clk_assert_reg(struct clk_hw *clk_hw, u32 reg) From 6d0adeca7cba7a1173715be0a36a564473365754 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 26 Mar 2021 17:00:15 +0100 Subject: [PATCH 23/31] dt-bindings: clock: Fix litex,clock - Drop unneeded "|" from descriptions, - Join single-line descriptions with keyword, - Drop bogus blank lines, - Add missing $ref for non-standard properties, - Fix spelling of "minimum" and "maximum", - Use patternProperties and "type: object" to describe subnodes, - Add missing "additionalProperties: false", - Use "clock-controller" from generic node names recommendation, - Group tuples in reg property in example, - Add missing unit address to subnodes, Signed-off-by: Geert Uytterhoeven --- .../bindings/clock/litex,clock.yaml | 104 ++++++++---------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/litex,clock.yaml b/Documentation/devicetree/bindings/clock/litex,clock.yaml index cd234fa4132af8..619ee31afa34b0 100644 --- a/Documentation/devicetree/bindings/clock/litex,clock.yaml +++ b/Documentation/devicetree/bindings/clock/litex,clock.yaml @@ -23,31 +23,27 @@ properties: description: Base address and lengths of the register space "#clock-cells": - description: | + description: Number of cells in a clock specifier; Typically 0 for nodes with a single clock output and 1 for nodes with multiple clock outputs. - const: 1 "#address-cells": - description: | + description: Number of cells that are needed to form the base address part in the reg property. - const: 1 "#size-cells": - description: | + description: Used to state how many cells are in each field of a reg property - const: 0 clock-output-names: - description: | + description: List of strings of clock output signal names indexed by the first cell in the clock specifier. - minItems: 1 maxItems: 7 items: @@ -60,67 +56,61 @@ properties: - const: CLKOUT6 litex,nclkout: - description: | - Number of desired clock outputs - - min: 1 - max: 7 + description: Number of desired clock outputs + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 7 litex,lock-timeout: - description: | - Number of ms to wait for MMCM to assert LOCK signal - - min: 1 + description: Number of ms to wait for MMCM to assert LOCK signal + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 litex,drdy-timeout: - description: | - Number of ms to wait for MMCM to assert DRDY signal - - min: 1 + description: Number of ms to wait for MMCM to assert DRDY signal + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 litex,sys-clock-freq: - description: | - System clock frequency + description: System clock frequency + $ref: /schemas/types.yaml#/definitions/uint32 - CLKOUTx: - description: | +patternProperties: + "^CLKOUT[0-6]$": + description: Child node representing configurable clock outputs of MMCM unit + type: object properties: compatible: const: litex,clock reg: - description: | - clock output ID, zero-based numbering + description: clock output ID, zero-based numbering litex,clock-frequency: - description: | - default frequency in Hz for clock output - - min: 1587000 - max: 100000000 + description: default frequency in Hz for clock output + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1587000 + maximum: 100000000 litex,clock-phase: - description: | - default phase offset given in degrees - - min: 0 - max: 359 + description: default phase offset given in degrees + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 359 litex,clock-duty-num: - description: | - default duty cycle numerator value - - min: 1 - max: 100 + description: default duty cycle numerator value + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 100 litex,clock-duty-den: - description: | - default duty cycle denominator value - - min: 1 - max: 100 + description: default duty cycle denominator value + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 100 required: - compatible @@ -142,11 +132,13 @@ required: - litex,nclkout - CLKOUTx +additionalProperties: false + examples: - | - clk0: clk@f0003000 { + clk0: clock-controller@f0003000 { compatible = "litex,clk"; - reg = <0x0 0xf0003000 0x0 0x100>; + reg = <0x0 0xf0003000>, <0x0 0x100>; #clock-cells = <1>; #address-cells = <1>; #size-cells = <0>; @@ -159,7 +151,7 @@ examples: "CLKOUT6"; litex,nclkout = <7>; - CLKOUT0: CLKOUT0 { + CLKOUT0: CLKOUT0@0 { compatible = "litex,clk"; #clock-cells = <0>; clock-output-names = "CLKOUT0"; @@ -169,7 +161,7 @@ examples: litex,clock-duty = <50>; }; - CLKOUT1: CLKOUT1 { + CLKOUT1: CLKOUT1@1 { compatible = "litex,clk"; #clock-cells = <0>; clock-output-names = "CLKOUT1"; @@ -179,7 +171,7 @@ examples: litex,clock-duty = <50>; }; - CLKOUT2: CLKOUT2 { + CLKOUT2: CLKOUT2@2 { compatible = "litex,clk"; #clock-cells = <0>; clock-output-names = "CLKOUT2"; @@ -189,7 +181,7 @@ examples: litex,clock-duty = <25>; }; - CLKOUT3: CLKOUT3 { + CLKOUT3: CLKOUT3@3 { compatible = "litex,clk"; #clock-cells = <0>; clock-output-names = "CLKOUT3"; @@ -199,7 +191,7 @@ examples: litex,clock-duty = <75>; }; - CLKOUT4: CLKOUT4 { + CLKOUT4: CLKOUT4@4 { compatible = "litex,clk"; #clock-cells = <0>; clock-output-names = "CLKOUT4"; @@ -209,7 +201,7 @@ examples: litex,clock-duty = <50>; }; - CLKOUT5: CLKOUT5 { + CLKOUT5: CLKOUT5@5 { compatible = "litex,clk"; #clock-cells = <0>; clock-output-names = "CLKOUT5"; @@ -219,7 +211,7 @@ examples: litex,clock-duty = <50>; }; - CLKOUT6: CLKOUT6 { + CLKOUT6: CLKOUT6@6 { compatible = "litex,clk"; #clock-cells = <0>; clock-output-names = "CLKOUT6"; From 4a9c51eae09dfa80f7f2492d9854c3d239070953 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Mon, 11 Jan 2021 10:19:17 -0500 Subject: [PATCH 24/31] litex_mmc: clarify how/why bus-width is configured LiteSDCard does not support the "standard" 1-bit bus width that cards (and the Linux mmc subsystem) both use as a default. As such, the card's bus-width must be set to 4-bit (the only width supported by LiteSDCard) before the very first data transfer occurs (which is typically much earlier than when the Linux mmc subsystem would switch to "wide mode". Also, if we're only supporting one bus width, there's no point in storing it as a variable in the "litex_mmc_host" data structure. Signed-off-by: Gabriel Somlo --- drivers/mmc/host/litex_mmc.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/host/litex_mmc.c b/drivers/mmc/host/litex_mmc.c index f6e0a48656295c..c51f4f1ec58d44 100644 --- a/drivers/mmc/host/litex_mmc.c +++ b/drivers/mmc/host/litex_mmc.c @@ -80,7 +80,6 @@ struct litex_mmc_host { unsigned int freq; unsigned int clock; - unsigned char bus_width; bool is_bus_width_set; bool app_cmd; }; @@ -190,7 +189,8 @@ static int litex_set_bus_width(struct litex_mmc_host *host) { if (!app_cmd_sent) send_app_cmd(host); - status = send_app_set_bus_width_cmd(host, host->bus_width); + /* litesdcard only supports 4-bit bus width */ + status = send_app_set_bus_width_cmd(host, MMC_BUS_WIDTH_4); /* re-send 'app_cmd' if necessary */ if (app_cmd_sent) @@ -247,8 +247,11 @@ static void litex_request(struct mmc_host *mmc, struct mmc_request *mrq) } if (data) { - // This is a good time (i.e. last moment) to finally set bus - // width. Ofc if not set yet. + /* LiteSDCard only supports 4-bit bus width; therefore, we MUST + * inject a SET_BUS_WIDTH (acmd6) before the very first data + * transfer, earlier than when the mmc subsystem would normally + * get around to it! + */ if (!host->is_bus_width_set) { ulong n = jiffies + 2 * HZ; // 500ms timeout while (litex_set_bus_width(host) != SD_OK) { @@ -356,17 +359,18 @@ static void litex_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct litex_mmc_host *host = mmc_priv(mmc); + /* updated ios->bus_width -- do nothing; + * This happens right after the mmc core subsystem has sent its + * own acmd6 to notify the card of the bus-width change, and it's + * effectively a no-op given that we already forced bus-width to 4 + * by snooping on the command flow, and inserting an acmd6 before + * the first data xfer comand! + */ + if (ios->clock != host->clock) { sdclk_set_clk(host, ios->clock); host->clock = ios->clock; } - - // Technically there should be some code forsetting requested bus width. - // We can't send "send_bus_width" command to the card at this moment - // because the card is not in the right state (the command is only accepted - // in "trans" state). Requested width should be stored and send when it is - // possible (see litex_request), but litesdcard supports only 4 bit bus - // width, so it is known what must be set. } static const struct mmc_host_ops litex_mmc_ops = { @@ -418,8 +422,10 @@ static int litex_mmc_probe(struct platform_device *pdev) if (ret) goto err_exit; - // litesdcard only supports 4-bit bus width - host->bus_width = MMC_BUS_WIDTH_4; + /* LiteSDCard only supports 4-bit bus width; therefore, we MUST inject + * a SET_BUS_WIDTH (acmd6) before the very first data transfer, earlier + * than when the mmc subsystem would normally get around to it! + */ host->is_bus_width_set = false; host->app_cmd = false; From 94d261d92d69f1ec8c50b985e09dd28792b9e258 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Mon, 11 Jan 2021 10:48:08 -0500 Subject: [PATCH 25/31] litex_mmc: don't overwrite capabilities parsed by mmc_of_parse() Instead, add to the parsed capabilities using the '|=' operator. FIXME: for now, MMC_CAP_NEEDS_POLL is set manually rather than by setting "broken-cd" in DTS. Further investigation (and possibly implementation of IRQs) is needeed to make a final determination of what the right approach is in this situation. Signed-off-by: Gabriel Somlo --- drivers/mmc/host/litex_mmc.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/litex_mmc.c b/drivers/mmc/host/litex_mmc.c index c51f4f1ec58d44..49703c085c9b57 100644 --- a/drivers/mmc/host/litex_mmc.c +++ b/drivers/mmc/host/litex_mmc.c @@ -449,14 +449,17 @@ static int litex_mmc_probe(struct platform_device *pdev) if (ret) goto err_exit; - mmc->caps = MMC_CAP_NEEDS_POLL | /* FIXME: we need this if no IRQ! */ - MMC_CAP_WAIT_WHILE_BUSY | - MMC_CAP_DRIVER_TYPE_D; - mmc->caps2 = MMC_CAP2_NO_SDIO | - MMC_CAP2_FULL_PWR_CYCLE | - MMC_CAP2_NO_WRITE_PROTECT; - mmc->ops = &litex_mmc_ops; + /* add set-by-default capabilities */ + mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_DRIVER_TYPE_D; + /* FIXME: set "broken-cd" in dt, or somehow handle through irq? */ + mmc->caps |= MMC_CAP_NEEDS_POLL; + /* default to "disable-wp", "full-pwr-cycle", "no-sdio" */ + mmc->caps2 |= MMC_CAP2_NO_WRITE_PROTECT | + MMC_CAP2_FULL_PWR_CYCLE | + MMC_CAP2_NO_SDIO; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->ops = &litex_mmc_ops; mmc->max_blk_size = DATA_BLOCK_SIZE; mmc->max_blk_count = MAX_NR_BLOCKS; From 19bf4a6280a9c403403375b4eea410332d18142f Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Thu, 14 Jan 2021 05:39:53 -0500 Subject: [PATCH 26/31] litex-mmc: ensure probe fails with appropriate code & log message Signed-off-by: Gabriel Somlo --- drivers/mmc/host/litex_mmc.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/litex_mmc.c b/drivers/mmc/host/litex_mmc.c index 49703c085c9b57..866be1d5ae6e52 100644 --- a/drivers/mmc/host/litex_mmc.c +++ b/drivers/mmc/host/litex_mmc.c @@ -385,6 +385,7 @@ static const struct mmc_host_ops litex_mmc_ops = { host->res_name = devm_ioremap_resource(&pdev->dev, res); \ if (IS_ERR(host->res_name)) { \ ret = PTR_ERR(host->res_name); \ + pr_err("MAP_RESOURCE %d failed\n", idx); \ goto err_exit; \ } \ } @@ -419,8 +420,10 @@ static int litex_mmc_probe(struct platform_device *pdev) cpu = of_find_node_by_name(NULL, "cpu"); ret = of_property_read_u32(cpu, "clock-frequency", &host->freq); of_node_put(cpu); - if (ret) + if (ret) { + pr_err("Couldn't find \"clock-frequency\" property in DT\n"); goto err_exit; + } /* LiteSDCard only supports 4-bit bus width; therefore, we MUST inject * a SET_BUS_WIDTH (acmd6) before the very first data transfer, earlier @@ -431,14 +434,18 @@ static int litex_mmc_probe(struct platform_device *pdev) if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))){ pr_err("Unable to set DMA driver failed\n"); + ret = -EINVAL; goto err_exit; } host->buffer_size = MAX_NR_BLOCKS * DATA_BLOCK_SIZE * 2; host->buffer = dma_alloc_coherent(&pdev->dev, host->buffer_size, &host->dma_handle, GFP_DMA); - if (host->buffer == NULL) + if (host->buffer == NULL) { + pr_err("could not allocate dma buffer\n"); + ret = -ENOMEM; goto err_exit; + } MAP_RESOURCE(sdphy, 0); MAP_RESOURCE(sdcore, 1); @@ -446,8 +453,10 @@ static int litex_mmc_probe(struct platform_device *pdev) MAP_RESOURCE(sdwriter, 3); ret = mmc_of_parse(mmc); - if (ret) + if (ret) { + pr_err("couldn't parse DT node\n"); goto err_exit; + } /* add set-by-default capabilities */ mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_DRIVER_TYPE_D; @@ -474,6 +483,7 @@ static int litex_mmc_probe(struct platform_device *pdev) ret = mmc_add_host(mmc); if (ret < 0) { + pr_err("mmc_add_host() failed\n"); goto err_exit; } From 3a07b7dcd7915ce4c1d7a4d759982434e1a4a37c Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Wed, 17 Feb 2021 17:16:52 -0500 Subject: [PATCH 27/31] litex_mmc: align internal error checks to hw. "event register" layout --- drivers/mmc/host/litex_mmc.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/litex_mmc.c b/drivers/mmc/host/litex_mmc.c index 866be1d5ae6e52..cd58603aca8b89 100644 --- a/drivers/mmc/host/litex_mmc.c +++ b/drivers/mmc/host/litex_mmc.c @@ -54,9 +54,10 @@ #define SDCARD_CTRL_RESPONSE_LONG 2 #define SD_OK 0 -#define SD_CRCERROR 1 +#define SD_WRITEERROR 1 #define SD_TIMEOUT 2 -#define SD_WRITEERROR 3 +#define SD_CRCERROR 3 +#define SD_ERR_OTHER 4 #define MAX_NR_SEGS 1 #define MAX_NR_BLOCKS 1 @@ -104,11 +105,16 @@ static int sdcard_wait_done(void __iomem *reg) { break; udelay(5); } + if (evt == 0x1) + return SD_OK; + if (evt & 0x2) + return SD_WRITEERROR; if (evt & 0x4) return SD_TIMEOUT; if (evt & 0x8) return SD_CRCERROR; - return SD_OK; + pr_err("sdcard_wait_done: unknown error evt=%x\n", evt); + return SD_ERR_OTHER; } static int send_cmd(struct litex_mmc_host *host, u8 cmd, u32 arg, @@ -318,15 +324,15 @@ static void litex_request(struct mmc_host *mmc, struct mmc_request *mrq) case SD_OK: cmd->error = 0; break; + case SD_WRITEERROR: + cmd->error = -EIO; + break; case SD_TIMEOUT: cmd->error = -ETIMEDOUT; break; case SD_CRCERROR: cmd->error = -EILSEQ; break; - case SD_WRITEERROR: - cmd->error = -EINVAL; - break; default: cmd->error = -EINVAL; break; From 504079db58b1ded1ec209565fbb9fae9f8ac1e24 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Mon, 11 Jan 2021 10:39:45 -0500 Subject: [PATCH 28/31] litex_mmc: redo max_[seg,req]_size, max_blk_count settings mmc_alloc_host() initializes max_[req,seg]_size = PAGE_SIZE, max_blk_size = 512, and max_blk_count accordingly (to PAGE_SIZE/512 = 8). Signed-off-by: Gabriel Somlo --- drivers/mmc/host/litex_mmc.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/host/litex_mmc.c b/drivers/mmc/host/litex_mmc.c index cd58603aca8b89..303666a520c75c 100644 --- a/drivers/mmc/host/litex_mmc.c +++ b/drivers/mmc/host/litex_mmc.c @@ -59,10 +59,6 @@ #define SD_CRCERROR 3 #define SD_ERR_OTHER 4 -#define MAX_NR_SEGS 1 -#define MAX_NR_BLOCKS 1 -#define DATA_BLOCK_SIZE 512 - struct litex_mmc_host { struct mmc_host *mmc; struct platform_device *dev; @@ -351,7 +347,7 @@ static void litex_request(struct mmc_host *mmc, struct mmc_request *mrq) if (status == SD_OK && transfer != SDCARD_CTRL_DATA_TRANSFER_NONE) { data->bytes_xfered = min(data->blksz * data->blocks, - (u32)(MAX_NR_BLOCKS*DATA_BLOCK_SIZE)); + mmc->max_req_size); if (transfer == SDCARD_CTRL_DATA_TRANSFER_READ) { sg_copy_from_buffer(data->sg, sg_nents(data->sg), host->buffer, data->bytes_xfered); @@ -414,6 +410,11 @@ static int litex_mmc_probe(struct platform_device *pdev) return -ENOMEM; mmc = mmc_alloc_host(sizeof(struct litex_mmc_host), &pdev->dev); + /* NOTE: mmc_alloc_host() defaults to max_[req,seg]_size=PAGE_SIZE, + * max_blk_size=512, and sets max_blk_count accordingly (to 8); If, + * for some reason, we want to modify max_blk_count, we must also + * re-calculate max_[req,seg]_size=max_blk_size*max_blk_count! + */ if (!mmc) return -ENOMEM; @@ -444,7 +445,7 @@ static int litex_mmc_probe(struct platform_device *pdev) goto err_exit; } - host->buffer_size = MAX_NR_BLOCKS * DATA_BLOCK_SIZE * 2; + host->buffer_size = mmc->max_req_size * 2; host->buffer = dma_alloc_coherent(&pdev->dev, host->buffer_size, &host->dma_handle, GFP_DMA); if (host->buffer == NULL) { @@ -476,12 +477,6 @@ static int litex_mmc_probe(struct platform_device *pdev) mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->ops = &litex_mmc_ops; - mmc->max_blk_size = DATA_BLOCK_SIZE; - mmc->max_blk_count = MAX_NR_BLOCKS; - mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; - mmc->max_segs = MAX_NR_SEGS; - mmc->max_seg_size = 512; - mmc->f_min = 125 * 1e5; // sys_clk/256 is minimal frequency mmcm can produce, set minimal to 12.5Mhz on lower frequencies, sdcard sometimes do not initialize properly mmc->f_max = 50 * 1e6; // 50Mhz is max frequency sd card can support From b0ced35f118c5cb8ae11d09493fcf683fccdbbab Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Mon, 22 Mar 2021 14:59:53 -0400 Subject: [PATCH 29/31] litex-mmc: enable multi-block data transfers Enable MMC_[READ|WRITE]_MULTIPLE_BLOCK commands to work properly in the driver, by ensuring each occurrence is followed up by a cmd-12 (MMC_STOP_TRANSMISSION). FIXME: need to figure out if there's a "canonical" way to tell the core mmc subsystem to issue these commands for us automatically, rather than having to hard-code the behavior in the driver ourselves! Signed-off-by: Gabriel Somlo --- drivers/mmc/host/litex_mmc.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/mmc/host/litex_mmc.c b/drivers/mmc/host/litex_mmc.c index 303666a520c75c..757238f071a417 100644 --- a/drivers/mmc/host/litex_mmc.c +++ b/drivers/mmc/host/litex_mmc.c @@ -168,6 +168,13 @@ static int send_cmd(struct litex_mmc_host *host, u8 cmd, u32 arg, return status; } +// CMD12 +static inline int send_stop_tx_cmd(struct litex_mmc_host *host) { + return send_cmd(host, MMC_STOP_TRANSMISSION, 0, + SDCARD_CTRL_RESPONSE_SHORT, + SDCARD_CTRL_DATA_TRANSFER_NONE); +} + // CMD55 static inline int send_app_cmd(struct litex_mmc_host *host) { return send_cmd(host, MMC_APP_CMD, host->rca << 16, @@ -316,6 +323,16 @@ static void litex_request(struct mmc_host *mmc, struct mmc_request *mrq) response_len, transfer); } while (status != SD_OK && retries-- > 0); + /* Each multi-block data transfer MUST be followed by a cmd12 + * (MMC_STOP_TRANSMISSION). + * FIXME: figure out why we need to do this here explicitly, and + * whether there's a way (e.g., capability flag, possibly set via + * some DT property) to get the driver to do this automatically! + */ + if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK || + cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) + send_stop_tx_cmd(host); + switch (status) { case SD_OK: cmd->error = 0; From 3480dbaf2b93f081cfaec477b73c1d287f2276c7 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Fri, 2 Apr 2021 21:50:19 -0400 Subject: [PATCH 30/31] litex_mmc: force single-block data transfers Currently, reads greatly benefit from multi-block (cmd-18) transfers being sped up by a factor of 2. However, writes (particularly large ones) will cause the kernel and/or gateware to hang (whereas single-block large writes might fail silently, but allow the overall gateware/kernel combination to continue running). FIXME: this is currently under investigation... Signed-off-by: Gabriel Somlo --- drivers/mmc/host/litex_mmc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/host/litex_mmc.c b/drivers/mmc/host/litex_mmc.c index 757238f071a417..020c64617304f0 100644 --- a/drivers/mmc/host/litex_mmc.c +++ b/drivers/mmc/host/litex_mmc.c @@ -435,6 +435,11 @@ static int litex_mmc_probe(struct platform_device *pdev) if (!mmc) return -ENOMEM; + /* force single-block transfers only */ + mmc->max_blk_count = 1; + mmc->max_seg_size = mmc->max_req_size = + mmc->max_blk_size * mmc->max_blk_count; + host = mmc_priv(mmc); host->mmc = mmc; host->dev = pdev; From 521ce2bcdf0302da8c0582771ae792aeab23ea51 Mon Sep 17 00:00:00 2001 From: Marek Czerski Date: Thu, 4 Mar 2021 00:56:28 +0100 Subject: [PATCH 31/31] spi: litespi: fix litespi cs handling for bulk transfers For bulk transfers it is essential to keep cs asserted for all transfers in a block. This is done by the higher level api trough set_cs callback. Changes in this commit require changes in the HDL part (https://github.com/enjoy-digital/litex) in the SPIMaster module, namely CS register must directly drive the cs pin. --- drivers/spi/spi-litespi.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-litespi.c b/drivers/spi/spi-litespi.c index 3ddbcfde634d36..f742860dec9be2 100644 --- a/drivers/spi/spi-litespi.c +++ b/drivers/spi/spi-litespi.c @@ -46,6 +46,20 @@ static inline void litespi_wait_xfer_end(struct litespi_hw *hw) cpu_relax(); } +static void litespi_cs(struct spi_device *spi, bool enable) +{ + struct litespi_hw *hw = spi_master_get_devdata(spi->master); + u8 cs = litex_read8(hw->base + LITESPI_OFF_CS); + if (!enable) {// inversed logic + cs |= BIT(spi->chip_select); + } + else { + cs &= ~BIT(spi->chip_select); + } + + litex_write8(hw->base + LITESPI_OFF_CS, cs); +} + static int litespi_rxtx(struct spi_master *master, struct spi_device *spi, struct spi_transfer *t) { @@ -53,9 +67,6 @@ static int litespi_rxtx(struct spi_master *master, struct spi_device *spi, u16 ctl_word = t->bits_per_word << LITESPI_CTRL_SHIFT_BPW; int i; - /* set chip select */ - litex_write8(hw->base + LITESPI_OFF_CS, BIT(spi->chip_select)); - /* set word size */ litex_write16(hw->base + LITESPI_OFF_CTRL, ctl_word); @@ -137,6 +148,7 @@ static int litespi_probe(struct platform_device *pdev) master->dev.of_node = pdev->dev.of_node; master->bus_num = pdev->id; + master->set_cs = litespi_cs; master->transfer_one = litespi_rxtx; master->mode_bits = SPI_MODE_0 | SPI_CS_HIGH; master->flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX;