diff options
Diffstat (limited to 'drivers/spi/spi-s3c64xx.c')
-rw-r--r-- | drivers/spi/spi-s3c64xx.c | 228 |
1 files changed, 171 insertions, 57 deletions
diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 652eadbefe..f726d86704 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -11,12 +11,14 @@ #include <linux/dmaengine.h> #include <linux/init.h> #include <linux/interrupt.h> +#include <linux/io.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_data/spi-s3c64xx.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/spi/spi.h> +#include <linux/types.h> #define MAX_SPI_PORTS 12 #define S3C64XX_SPI_QUIRK_CS_AUTO (1 << 1) @@ -77,6 +79,8 @@ #define S3C64XX_SPI_INT_RX_FIFORDY_EN (1<<1) #define S3C64XX_SPI_INT_TX_FIFORDY_EN (1<<0) +#define S3C64XX_SPI_ST_RX_FIFO_RDY_V2 GENMASK(23, 15) +#define S3C64XX_SPI_ST_TX_FIFO_RDY_V2 GENMASK(14, 6) #define S3C64XX_SPI_ST_TX_FIFO_LVL_SHIFT 6 #define S3C64XX_SPI_ST_RX_OVERRUN_ERR (1<<5) #define S3C64XX_SPI_ST_RX_UNDERRUN_ERR (1<<4) @@ -117,8 +121,6 @@ #define S3C64XX_SPI_MAX_TRAILCNT 0x3ff #define S3C64XX_SPI_TRAILCNT_OFF 19 -#define S3C64XX_SPI_TRAILCNT S3C64XX_SPI_MAX_TRAILCNT - #define S3C64XX_SPI_POLLING_SIZE 32 #define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) @@ -135,8 +137,9 @@ struct s3c64xx_spi_dma_data { /** * struct s3c64xx_spi_port_config - SPI Controller hardware info - * @fifo_lvl_mask: Bit-mask for {TX|RX}_FIFO_LVL bits in SPI_STATUS register. - * @rx_lvl_offset: Bit offset of RX_FIFO_LVL bits in SPI_STATUS regiter. + * @fifo_lvl_mask: [DEPRECATED] use @{rx, tx}_fifomask instead. + * @rx_lvl_offset: [DEPRECATED] use @{rx,tx}_fifomask instead. + * @fifo_depth: depth of the FIFO. * @rx_fifomask: SPI_STATUS.RX_FIFO_LVL mask. Shifted mask defining the field's * length and position. * @tx_fifomask: SPI_STATUS.TX_FIFO_LVL mask. Shifted mask defining the field's @@ -149,6 +152,7 @@ struct s3c64xx_spi_dma_data { * prescaler unit. * @clk_ioclk: True if clock is present on this device * @has_loopback: True if loopback mode can be supported + * @use_32bit_io: True if the SoC allows only 32-bit register accesses. * * The Samsung s3c64xx SPI controller are used on various Samsung SoC's but * differ in some aspects such as the size of the fifo and spi bus clock @@ -158,6 +162,7 @@ struct s3c64xx_spi_dma_data { struct s3c64xx_spi_port_config { int fifo_lvl_mask[MAX_SPI_PORTS]; int rx_lvl_offset; + unsigned int fifo_depth; u32 rx_fifomask; u32 tx_fifomask; int tx_st_done; @@ -167,6 +172,7 @@ struct s3c64xx_spi_port_config { bool clk_from_cmu; bool clk_ioclk; bool has_loopback; + bool use_32bit_io; }; /** @@ -187,8 +193,8 @@ struct s3c64xx_spi_port_config { * @cur_speed: Current clock speed * @rx_dma: Local receive DMA data (e.g. chan and direction) * @tx_dma: Local transmit DMA data (e.g. chan and direction) - * @port_conf: Local SPI port configuartion data - * @port_id: Port identification number + * @port_conf: Local SPI port configuration data + * @port_id: [DEPRECATED] use @{rx,tx}_fifomask instead. * @fifo_depth: depth of the FIFO. * @rx_fifomask: SPI_STATUS.RX_FIFO_LVL mask. Shifted mask defining the field's * length and position. @@ -294,8 +300,8 @@ static void s3c64xx_spi_dmacb(void *data) spin_unlock_irqrestore(&sdd->lock, flags); } -static int prepare_dma(struct s3c64xx_spi_dma_data *dma, - struct sg_table *sgt) +static int s3c64xx_prepare_dma(struct s3c64xx_spi_dma_data *dma, + struct sg_table *sgt) { struct s3c64xx_spi_driver_data *sdd; struct dma_slave_config config; @@ -307,20 +313,20 @@ static int prepare_dma(struct s3c64xx_spi_dma_data *dma, if (dma->direction == DMA_DEV_TO_MEM) { sdd = container_of((void *)dma, struct s3c64xx_spi_driver_data, rx_dma); - config.direction = dma->direction; config.src_addr = sdd->sfr_start + S3C64XX_SPI_RX_DATA; config.src_addr_width = sdd->cur_bpw / 8; config.src_maxburst = 1; - dmaengine_slave_config(dma->ch, &config); } else { sdd = container_of((void *)dma, struct s3c64xx_spi_driver_data, tx_dma); - config.direction = dma->direction; config.dst_addr = sdd->sfr_start + S3C64XX_SPI_TX_DATA; config.dst_addr_width = sdd->cur_bpw / 8; config.dst_maxburst = 1; - dmaengine_slave_config(dma->ch, &config); } + config.direction = dma->direction; + ret = dmaengine_slave_config(dma->ch, &config); + if (ret) + return ret; desc = dmaengine_prep_slave_sg(dma->ch, sgt->sgl, sgt->nents, dma->direction, DMA_PREP_INTERRUPT); @@ -337,7 +343,7 @@ static int prepare_dma(struct s3c64xx_spi_dma_data *dma, ret = dma_submit_error(dma->cookie); if (ret) { dev_err(&sdd->pdev->dev, "DMA submission failed"); - return -EIO; + return ret; } dma_async_issue_pending(dma->ch); @@ -429,6 +435,56 @@ static bool s3c64xx_spi_can_dma(struct spi_controller *host, return false; } +static void s3c64xx_iowrite8_32_rep(volatile void __iomem *addr, + const void *buffer, unsigned int count) +{ + if (count) { + const u8 *buf = buffer; + + do { + __raw_writel(*buf++, addr); + } while (--count); + } +} + +static void s3c64xx_iowrite16_32_rep(volatile void __iomem *addr, + const void *buffer, unsigned int count) +{ + if (count) { + const u16 *buf = buffer; + + do { + __raw_writel(*buf++, addr); + } while (--count); + } +} + +static void s3c64xx_iowrite_rep(const struct s3c64xx_spi_driver_data *sdd, + struct spi_transfer *xfer) +{ + void __iomem *addr = sdd->regs + S3C64XX_SPI_TX_DATA; + const void *buf = xfer->tx_buf; + unsigned int len = xfer->len; + + switch (sdd->cur_bpw) { + case 32: + iowrite32_rep(addr, buf, len / 4); + break; + case 16: + if (sdd->port_conf->use_32bit_io) + s3c64xx_iowrite16_32_rep(addr, buf, len / 2); + else + iowrite16_rep(addr, buf, len / 2); + break; + default: + if (sdd->port_conf->use_32bit_io) + s3c64xx_iowrite8_32_rep(addr, buf, len); + else + iowrite8_rep(addr, buf, len); + break; + } +} + static int s3c64xx_enable_datapath(struct s3c64xx_spi_driver_data *sdd, struct spi_transfer *xfer, int dma_mode) { @@ -460,22 +516,9 @@ static int s3c64xx_enable_datapath(struct s3c64xx_spi_driver_data *sdd, chcfg |= S3C64XX_SPI_CH_TXCH_ON; if (dma_mode) { modecfg |= S3C64XX_SPI_MODE_TXDMA_ON; - ret = prepare_dma(&sdd->tx_dma, &xfer->tx_sg); + ret = s3c64xx_prepare_dma(&sdd->tx_dma, &xfer->tx_sg); } else { - switch (sdd->cur_bpw) { - case 32: - iowrite32_rep(regs + S3C64XX_SPI_TX_DATA, - xfer->tx_buf, xfer->len / 4); - break; - case 16: - iowrite16_rep(regs + S3C64XX_SPI_TX_DATA, - xfer->tx_buf, xfer->len / 2); - break; - default: - iowrite8_rep(regs + S3C64XX_SPI_TX_DATA, - xfer->tx_buf, xfer->len); - break; - } + s3c64xx_iowrite_rep(sdd, xfer); } } @@ -492,7 +535,7 @@ static int s3c64xx_enable_datapath(struct s3c64xx_spi_driver_data *sdd, writel(((xfer->len * 8 / sdd->cur_bpw) & 0xffff) | S3C64XX_SPI_PACKET_CNT_EN, regs + S3C64XX_SPI_PACKET_CNT); - ret = prepare_dma(&sdd->rx_dma, &xfer->rx_sg); + ret = s3c64xx_prepare_dma(&sdd->rx_dma, &xfer->rx_sg); } } @@ -542,7 +585,7 @@ static int s3c64xx_wait_for_dma(struct s3c64xx_spi_driver_data *sdd, /* * If the previous xfer was completed within timeout, then - * proceed further else return -EIO. + * proceed further else return -ETIMEDOUT. * DmaTx returns after simply writing data in the FIFO, * w/o waiting for real transmission on the bus to finish. * DmaRx returns only after Dma read data from FIFO which @@ -563,7 +606,7 @@ static int s3c64xx_wait_for_dma(struct s3c64xx_spi_driver_data *sdd, /* If timed out while checking rx/tx status return error */ if (!val) - return -EIO; + return -ETIMEDOUT; return 0; } @@ -593,7 +636,7 @@ static int s3c64xx_wait_for_pio(struct s3c64xx_spi_driver_data *sdd, if (use_irq) { val = msecs_to_jiffies(ms); if (!wait_for_completion_timeout(&sdd->xfer_completion, val)) - return -EIO; + return -ETIMEDOUT; } val = msecs_to_loops(ms); @@ -1106,8 +1149,7 @@ static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd) val = readl(regs + S3C64XX_SPI_MODE_CFG); val &= ~S3C64XX_SPI_MODE_4BURST; - val &= ~(S3C64XX_SPI_MAX_TRAILCNT << S3C64XX_SPI_TRAILCNT_OFF); - val |= (S3C64XX_SPI_TRAILCNT << S3C64XX_SPI_TRAILCNT_OFF); + val |= (S3C64XX_SPI_MAX_TRAILCNT << S3C64XX_SPI_TRAILCNT_OFF); writel(val, regs + S3C64XX_SPI_MODE_CFG); s3c64xx_flush_fifo(sdd); @@ -1124,14 +1166,14 @@ static struct s3c64xx_spi_info *s3c64xx_spi_parse_dt(struct device *dev) return ERR_PTR(-ENOMEM); if (of_property_read_u32(dev->of_node, "samsung,spi-src-clk", &temp)) { - dev_warn(dev, "spi bus clock parent not specified, using clock at index 0 as parent\n"); + dev_dbg(dev, "spi bus clock parent not specified, using clock at index 0 as parent\n"); sci->src_clk_nr = 0; } else { sci->src_clk_nr = temp; } if (of_property_read_u32(dev->of_node, "num-cs", &temp)) { - dev_warn(dev, "number of chip select lines not specified, assuming 1 chip select line\n"); + dev_dbg(dev, "number of chip select lines not specified, assuming 1 chip select line\n"); sci->num_cs = 1; } else { sci->num_cs = temp; @@ -1159,6 +1201,31 @@ static inline const struct s3c64xx_spi_port_config *s3c64xx_spi_get_port_config( return (const struct s3c64xx_spi_port_config *)platform_get_device_id(pdev)->driver_data; } +static int s3c64xx_spi_set_port_id(struct platform_device *pdev, + struct s3c64xx_spi_driver_data *sdd) +{ + const struct s3c64xx_spi_port_config *port_conf = sdd->port_conf; + int ret; + + if (port_conf->rx_fifomask && port_conf->tx_fifomask) + return 0; + + if (pdev->dev.of_node) { + ret = of_alias_get_id(pdev->dev.of_node, "spi"); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Failed to get alias id\n"); + sdd->port_id = ret; + } else { + if (pdev->id < 0) + return dev_err_probe(&pdev->dev, -EINVAL, + "Negative platform ID is not allowed\n"); + sdd->port_id = pdev->id; + } + + return 0; +} + static void s3c64xx_spi_set_fifomask(struct s3c64xx_spi_driver_data *sdd) { const struct s3c64xx_spi_port_config *port_conf = sdd->port_conf; @@ -1211,17 +1278,16 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) sdd->host = host; sdd->cntrlr_info = sci; sdd->pdev = pdev; - if (pdev->dev.of_node) { - ret = of_alias_get_id(pdev->dev.of_node, "spi"); - if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "Failed to get alias id\n"); - sdd->port_id = ret; - } else { - sdd->port_id = pdev->id; - } - sdd->fifo_depth = FIFO_DEPTH(sdd); + ret = s3c64xx_spi_set_port_id(pdev, sdd); + if (ret) + return ret; + + if (sdd->port_conf->fifo_depth) + sdd->fifo_depth = sdd->port_conf->fifo_depth; + else if (of_property_read_u32(pdev->dev.of_node, "fifo-depth", + &sdd->fifo_depth)) + sdd->fifo_depth = FIFO_DEPTH(sdd); s3c64xx_spi_set_fifomask(sdd); @@ -1231,7 +1297,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) sdd->rx_dma.direction = DMA_DEV_TO_MEM; host->dev.of_node = pdev->dev.of_node; - host->bus_num = sdd->port_id; + host->bus_num = -1; host->setup = s3c64xx_spi_setup; host->cleanup = s3c64xx_spi_cleanup; host->prepare_transfer_hardware = s3c64xx_spi_prepare_transfer; @@ -1312,7 +1378,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) } dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d with %d Targets attached\n", - sdd->port_id, host->num_chipselect); + host->bus_num, host->num_chipselect); dev_dbg(&pdev->dev, "\tIOmem=[%pR]\tFIFO %dbytes\n", mem_res, sdd->fifo_depth); @@ -1353,8 +1419,9 @@ static int s3c64xx_spi_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct s3c64xx_spi_driver_data *sdd = spi_controller_get_devdata(host); + int ret; - int ret = spi_controller_suspend(host); + ret = spi_controller_suspend(host); if (ret) return ret; @@ -1442,7 +1509,9 @@ static const struct dev_pm_ops s3c64xx_spi_pm = { }; static const struct s3c64xx_spi_port_config s3c2443_spi_port_config = { + /* fifo_lvl_mask is deprecated. Use {rx, tx}_fifomask instead. */ .fifo_lvl_mask = { 0x7f }, + /* rx_lvl_offset is deprecated. Use {rx, tx}_fifomask instead. */ .rx_lvl_offset = 13, .tx_st_done = 21, .clk_div = 2, @@ -1450,14 +1519,18 @@ static const struct s3c64xx_spi_port_config s3c2443_spi_port_config = { }; static const struct s3c64xx_spi_port_config s3c6410_spi_port_config = { + /* fifo_lvl_mask is deprecated. Use {rx, tx}_fifomask instead. */ .fifo_lvl_mask = { 0x7f, 0x7F }, + /* rx_lvl_offset is deprecated. Use {rx, tx}_fifomask instead. */ .rx_lvl_offset = 13, .tx_st_done = 21, .clk_div = 2, }; static const struct s3c64xx_spi_port_config s5pv210_spi_port_config = { + /* fifo_lvl_mask is deprecated. Use {rx, tx}_fifomask instead. */ .fifo_lvl_mask = { 0x1ff, 0x7F }, + /* rx_lvl_offset is deprecated. Use {rx, tx}_fifomask instead. */ .rx_lvl_offset = 15, .tx_st_done = 25, .clk_div = 2, @@ -1465,7 +1538,9 @@ static const struct s3c64xx_spi_port_config s5pv210_spi_port_config = { }; static const struct s3c64xx_spi_port_config exynos4_spi_port_config = { + /* fifo_lvl_mask is deprecated. Use {rx, tx}_fifomask instead. */ .fifo_lvl_mask = { 0x1ff, 0x7F, 0x7F }, + /* rx_lvl_offset is deprecated. Use {rx, tx}_fifomask instead. */ .rx_lvl_offset = 15, .tx_st_done = 25, .clk_div = 2, @@ -1475,7 +1550,9 @@ static const struct s3c64xx_spi_port_config exynos4_spi_port_config = { }; static const struct s3c64xx_spi_port_config exynos7_spi_port_config = { + /* fifo_lvl_mask is deprecated. Use {rx, tx}_fifomask instead. */ .fifo_lvl_mask = { 0x1ff, 0x7F, 0x7F, 0x7F, 0x7F, 0x1ff}, + /* rx_lvl_offset is deprecated. Use {rx, tx}_fifomask instead. */ .rx_lvl_offset = 15, .tx_st_done = 25, .clk_div = 2, @@ -1485,7 +1562,9 @@ static const struct s3c64xx_spi_port_config exynos7_spi_port_config = { }; static const struct s3c64xx_spi_port_config exynos5433_spi_port_config = { + /* fifo_lvl_mask is deprecated. Use {rx, tx}_fifomask instead. */ .fifo_lvl_mask = { 0x1ff, 0x7f, 0x7f, 0x7f, 0x7f, 0x1ff}, + /* rx_lvl_offset is deprecated. Use {rx, tx}_fifomask instead. */ .rx_lvl_offset = 15, .tx_st_done = 25, .clk_div = 2, @@ -1495,9 +1574,23 @@ static const struct s3c64xx_spi_port_config exynos5433_spi_port_config = { .quirks = S3C64XX_SPI_QUIRK_CS_AUTO, }; +static const struct s3c64xx_spi_port_config exynos850_spi_port_config = { + .fifo_depth = 64, + .rx_fifomask = S3C64XX_SPI_ST_RX_FIFO_RDY_V2, + .tx_fifomask = S3C64XX_SPI_ST_TX_FIFO_RDY_V2, + .tx_st_done = 25, + .clk_div = 4, + .high_speed = true, + .clk_from_cmu = true, + .has_loopback = true, + .quirks = S3C64XX_SPI_QUIRK_CS_AUTO, +}; + static const struct s3c64xx_spi_port_config exynosautov9_spi_port_config = { + /* fifo_lvl_mask is deprecated. Use {rx, tx}_fifomask instead. */ .fifo_lvl_mask = { 0x1ff, 0x1ff, 0x7f, 0x7f, 0x7f, 0x7f, 0x1ff, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f}, + /* rx_lvl_offset is deprecated. Use {rx, tx}_fifomask instead. */ .rx_lvl_offset = 15, .tx_st_done = 25, .clk_div = 4, @@ -1509,7 +1602,9 @@ static const struct s3c64xx_spi_port_config exynosautov9_spi_port_config = { }; static const struct s3c64xx_spi_port_config fsd_spi_port_config = { + /* fifo_lvl_mask is deprecated. Use {rx, tx}_fifomask instead. */ .fifo_lvl_mask = { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f}, + /* rx_lvl_offset is deprecated. Use {rx, tx}_fifomask instead. */ .rx_lvl_offset = 15, .tx_st_done = 25, .clk_div = 2, @@ -1519,6 +1614,19 @@ static const struct s3c64xx_spi_port_config fsd_spi_port_config = { .quirks = S3C64XX_SPI_QUIRK_CS_AUTO, }; +static const struct s3c64xx_spi_port_config gs101_spi_port_config = { + .fifo_depth = 64, + .rx_fifomask = S3C64XX_SPI_ST_RX_FIFO_RDY_V2, + .tx_fifomask = S3C64XX_SPI_ST_TX_FIFO_RDY_V2, + .tx_st_done = 25, + .clk_div = 4, + .high_speed = true, + .clk_from_cmu = true, + .has_loopback = true, + .use_32bit_io = true, + .quirks = S3C64XX_SPI_QUIRK_CS_AUTO, +}; + static const struct platform_device_id s3c64xx_spi_driver_ids[] = { { .name = "s3c2443-spi", @@ -1531,29 +1639,35 @@ static const struct platform_device_id s3c64xx_spi_driver_ids[] = { }; static const struct of_device_id s3c64xx_spi_dt_match[] = { + { .compatible = "google,gs101-spi", + .data = &gs101_spi_port_config, + }, { .compatible = "samsung,s3c2443-spi", - .data = (void *)&s3c2443_spi_port_config, + .data = &s3c2443_spi_port_config, }, { .compatible = "samsung,s3c6410-spi", - .data = (void *)&s3c6410_spi_port_config, + .data = &s3c6410_spi_port_config, }, { .compatible = "samsung,s5pv210-spi", - .data = (void *)&s5pv210_spi_port_config, + .data = &s5pv210_spi_port_config, }, { .compatible = "samsung,exynos4210-spi", - .data = (void *)&exynos4_spi_port_config, + .data = &exynos4_spi_port_config, }, { .compatible = "samsung,exynos7-spi", - .data = (void *)&exynos7_spi_port_config, + .data = &exynos7_spi_port_config, }, { .compatible = "samsung,exynos5433-spi", - .data = (void *)&exynos5433_spi_port_config, + .data = &exynos5433_spi_port_config, + }, + { .compatible = "samsung,exynos850-spi", + .data = &exynos850_spi_port_config, }, { .compatible = "samsung,exynosautov9-spi", - .data = (void *)&exynosautov9_spi_port_config, + .data = &exynosautov9_spi_port_config, }, { .compatible = "tesla,fsd-spi", - .data = (void *)&fsd_spi_port_config, + .data = &fsd_spi_port_config, }, { }, }; |