diff options
Diffstat (limited to 'drivers/spi/spi-s3c64xx.c')
-rw-r--r-- | drivers/spi/spi-s3c64xx.c | 206 |
1 files changed, 128 insertions, 78 deletions
diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 5a76a50063b5..3c09e94cf827 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -156,12 +156,14 @@ struct s3c64xx_spi_port_config { int quirks; bool high_speed; bool clk_from_cmu; + bool clk_ioclk; }; /** * struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver. * @clk: Pointer to the spi clock. * @src_clk: Pointer to the clock used to generate SPI signals. + * @ioclk: Pointer to the i/o clock between master and slave * @master: Pointer to the SPI Protocol master. * @cntrlr_info: Platform specific data for the controller this driver manages. * @tgl_spi: Pointer to the last CS left untoggled by the cs_change hint. @@ -181,6 +183,7 @@ struct s3c64xx_spi_driver_data { void __iomem *regs; struct clk *clk; struct clk *src_clk; + struct clk *ioclk; struct platform_device *pdev; struct spi_master *master; struct s3c64xx_spi_info *cntrlr_info; @@ -310,44 +313,63 @@ static void prepare_dma(struct s3c64xx_spi_dma_data *dma, dma_async_issue_pending(dma->ch); } +static void s3c64xx_spi_set_cs(struct spi_device *spi, bool enable) +{ + struct s3c64xx_spi_driver_data *sdd = + spi_master_get_devdata(spi->master); + + if (sdd->cntrlr_info->no_cs) + return; + + if (enable) { + if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) { + writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + } else { + u32 ssel = readl(sdd->regs + S3C64XX_SPI_SLAVE_SEL); + + ssel |= (S3C64XX_SPI_SLAVE_AUTO | + S3C64XX_SPI_SLAVE_NSC_CNT_2); + writel(ssel, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + } + } else { + if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) + writel(S3C64XX_SPI_SLAVE_SIG_INACT, + sdd->regs + S3C64XX_SPI_SLAVE_SEL); + } +} + static int s3c64xx_spi_prepare_transfer(struct spi_master *spi) { struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(spi); dma_filter_fn filter = sdd->cntrlr_info->filter; struct device *dev = &sdd->pdev->dev; dma_cap_mask_t mask; - int ret; - if (!is_polling(sdd)) { - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - - /* Acquire DMA channels */ - sdd->rx_dma.ch = dma_request_slave_channel_compat(mask, filter, - sdd->cntrlr_info->dma_rx, dev, "rx"); - if (!sdd->rx_dma.ch) { - dev_err(dev, "Failed to get RX DMA channel\n"); - ret = -EBUSY; - goto out; - } - spi->dma_rx = sdd->rx_dma.ch; - - sdd->tx_dma.ch = dma_request_slave_channel_compat(mask, filter, - sdd->cntrlr_info->dma_tx, dev, "tx"); - if (!sdd->tx_dma.ch) { - dev_err(dev, "Failed to get TX DMA channel\n"); - ret = -EBUSY; - goto out_rx; - } - spi->dma_tx = sdd->tx_dma.ch; + if (is_polling(sdd)) + return 0; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* Acquire DMA channels */ + sdd->rx_dma.ch = dma_request_slave_channel_compat(mask, filter, + sdd->cntrlr_info->dma_rx, dev, "rx"); + if (!sdd->rx_dma.ch) { + dev_err(dev, "Failed to get RX DMA channel\n"); + return -EBUSY; } + spi->dma_rx = sdd->rx_dma.ch; - return 0; + sdd->tx_dma.ch = dma_request_slave_channel_compat(mask, filter, + sdd->cntrlr_info->dma_tx, dev, "tx"); + if (!sdd->tx_dma.ch) { + dev_err(dev, "Failed to get TX DMA channel\n"); + dma_release_channel(sdd->rx_dma.ch); + return -EBUSY; + } + spi->dma_tx = sdd->tx_dma.ch; -out_rx: - dma_release_channel(sdd->rx_dma.ch); -out: - return ret; + return 0; } static int s3c64xx_spi_unprepare_transfer(struct spi_master *spi) @@ -577,9 +599,7 @@ static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd) u32 val; /* Disable Clock */ - if (sdd->port_conf->clk_from_cmu) { - clk_disable_unprepare(sdd->src_clk); - } else { + if (!sdd->port_conf->clk_from_cmu) { val = readl(regs + S3C64XX_SPI_CLK_CFG); val &= ~S3C64XX_SPI_ENCLK_ENABLE; writel(val, regs + S3C64XX_SPI_CLK_CFG); @@ -622,11 +642,8 @@ static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd) writel(val, regs + S3C64XX_SPI_MODE_CFG); if (sdd->port_conf->clk_from_cmu) { - /* Configure Clock */ - /* There is half-multiplier before the SPI */ + /* The src_clk clock is divided internally by 2 */ clk_set_rate(sdd->src_clk, sdd->cur_speed * 2); - /* Enable Clock */ - clk_prepare_enable(sdd->src_clk); } else { /* Configure Clock */ val = readl(regs + S3C64XX_SPI_CLK_CFG); @@ -651,16 +668,6 @@ static int s3c64xx_spi_prepare_message(struct spi_master *master, struct spi_device *spi = msg->spi; struct s3c64xx_spi_csinfo *cs = spi->controller_data; - /* If Master's(controller) state differs from that needed by Slave */ - if (sdd->cur_speed != spi->max_speed_hz - || sdd->cur_mode != spi->mode - || sdd->cur_bpw != spi->bits_per_word) { - sdd->cur_bpw = spi->bits_per_word; - sdd->cur_speed = spi->max_speed_hz; - sdd->cur_mode = spi->mode; - s3c64xx_spi_config(sdd); - } - /* Configure feedback delay */ writel(cs->fb_delay & 0x3, sdd->regs + S3C64XX_SPI_FB_CLK); @@ -687,6 +694,7 @@ static int s3c64xx_spi_transfer_one(struct spi_master *master, if (bpw != sdd->cur_bpw || speed != sdd->cur_speed) { sdd->cur_bpw = bpw; sdd->cur_speed = speed; + sdd->cur_mode = spi->mode; s3c64xx_spi_config(sdd); } @@ -706,12 +714,7 @@ static int s3c64xx_spi_transfer_one(struct spi_master *master, enable_datapath(sdd, spi, xfer, use_dma); /* Start the signals */ - if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) - writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); - else - writel(readl(sdd->regs + S3C64XX_SPI_SLAVE_SEL) - | S3C64XX_SPI_SLAVE_AUTO | S3C64XX_SPI_SLAVE_NSC_CNT_2, - sdd->regs + S3C64XX_SPI_SLAVE_SEL); + s3c64xx_spi_set_cs(spi, true); spin_unlock_irqrestore(&sdd->lock, flags); @@ -861,16 +864,15 @@ static int s3c64xx_spi_setup(struct spi_device *spi) pm_runtime_mark_last_busy(&sdd->pdev->dev); pm_runtime_put_autosuspend(&sdd->pdev->dev); - if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) - writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + s3c64xx_spi_set_cs(spi, false); + return 0; setup_exit: pm_runtime_mark_last_busy(&sdd->pdev->dev); pm_runtime_put_autosuspend(&sdd->pdev->dev); /* setup() returns with device de-selected */ - if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) - writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + s3c64xx_spi_set_cs(spi, false); if (gpio_is_valid(spi->cs_gpio)) gpio_free(spi->cs_gpio); @@ -944,7 +946,9 @@ static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd, int channel) sdd->cur_speed = 0; - if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) + if (sci->no_cs) + writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + else if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL); /* Disable Interrupts - we use Polling if not DMA mode */ @@ -999,6 +1003,8 @@ static struct s3c64xx_spi_info *s3c64xx_spi_parse_dt(struct device *dev) sci->num_cs = temp; } + sci->no_cs = of_property_read_bool(dev->of_node, "broken-cs"); + return sci; } #else @@ -1076,7 +1082,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) if (ret < 0) { dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret); - goto err0; + goto err_deref_master; } sdd->port_id = ret; } else { @@ -1114,13 +1120,13 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) sdd->regs = devm_ioremap_resource(&pdev->dev, mem_res); if (IS_ERR(sdd->regs)) { ret = PTR_ERR(sdd->regs); - goto err0; + goto err_deref_master; } if (sci->cfg_gpio && sci->cfg_gpio()) { dev_err(&pdev->dev, "Unable to config gpio\n"); ret = -EBUSY; - goto err0; + goto err_deref_master; } /* Setup clocks */ @@ -1128,13 +1134,13 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) if (IS_ERR(sdd->clk)) { dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n"); ret = PTR_ERR(sdd->clk); - goto err0; + goto err_deref_master; } - if (clk_prepare_enable(sdd->clk)) { + ret = clk_prepare_enable(sdd->clk); + if (ret) { dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n"); - ret = -EBUSY; - goto err0; + goto err_deref_master; } sprintf(clk_name, "spi_busclk%d", sci->src_clk_nr); @@ -1143,13 +1149,28 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Unable to acquire clock '%s'\n", clk_name); ret = PTR_ERR(sdd->src_clk); - goto err2; + goto err_disable_clk; } - if (clk_prepare_enable(sdd->src_clk)) { + ret = clk_prepare_enable(sdd->src_clk); + if (ret) { dev_err(&pdev->dev, "Couldn't enable clock '%s'\n", clk_name); - ret = -EBUSY; - goto err2; + goto err_disable_clk; + } + + if (sdd->port_conf->clk_ioclk) { + sdd->ioclk = devm_clk_get(&pdev->dev, "spi_ioclk"); + if (IS_ERR(sdd->ioclk)) { + dev_err(&pdev->dev, "Unable to acquire 'ioclk'\n"); + ret = PTR_ERR(sdd->ioclk); + goto err_disable_src_clk; + } + + ret = clk_prepare_enable(sdd->ioclk); + if (ret) { + dev_err(&pdev->dev, "Couldn't enable clock 'ioclk'\n"); + goto err_disable_src_clk; + } } pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT); @@ -1169,7 +1190,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) if (ret != 0) { dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n", irq, ret); - goto err3; + goto err_pm_put; } writel(S3C64XX_SPI_INT_RX_OVERRUN_EN | S3C64XX_SPI_INT_RX_UNDERRUN_EN | @@ -1179,7 +1200,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) ret = devm_spi_register_master(&pdev->dev, master); if (ret != 0) { dev_err(&pdev->dev, "cannot register SPI master: %d\n", ret); - goto err3; + goto err_pm_put; } dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d with %d Slaves attached\n", @@ -1193,15 +1214,17 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) return 0; -err3: +err_pm_put: pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); + clk_disable_unprepare(sdd->ioclk); +err_disable_src_clk: clk_disable_unprepare(sdd->src_clk); -err2: +err_disable_clk: clk_disable_unprepare(sdd->clk); -err0: +err_deref_master: spi_master_put(master); return ret; @@ -1209,13 +1232,15 @@ err0: static int s3c64xx_spi_remove(struct platform_device *pdev) { - struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); + struct spi_master *master = platform_get_drvdata(pdev); struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); pm_runtime_get_sync(&pdev->dev); writel(0, sdd->regs + S3C64XX_SPI_INT_EN); + clk_disable_unprepare(sdd->ioclk); + clk_disable_unprepare(sdd->src_clk); clk_disable_unprepare(sdd->clk); @@ -1274,6 +1299,7 @@ static int s3c64xx_spi_runtime_suspend(struct device *dev) clk_disable_unprepare(sdd->clk); clk_disable_unprepare(sdd->src_clk); + clk_disable_unprepare(sdd->ioclk); return 0; } @@ -1284,17 +1310,28 @@ static int s3c64xx_spi_runtime_resume(struct device *dev) struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); int ret; + if (sdd->port_conf->clk_ioclk) { + ret = clk_prepare_enable(sdd->ioclk); + if (ret != 0) + return ret; + } + ret = clk_prepare_enable(sdd->src_clk); if (ret != 0) - return ret; + goto err_disable_ioclk; ret = clk_prepare_enable(sdd->clk); - if (ret != 0) { - clk_disable_unprepare(sdd->src_clk); - return ret; - } + if (ret != 0) + goto err_disable_src_clk; return 0; + +err_disable_src_clk: + clk_disable_unprepare(sdd->src_clk); +err_disable_ioclk: + clk_disable_unprepare(sdd->ioclk); + + return ret; } #endif /* CONFIG_PM */ @@ -1350,6 +1387,16 @@ static struct s3c64xx_spi_port_config exynos7_spi_port_config = { .quirks = S3C64XX_SPI_QUIRK_CS_AUTO, }; +static struct s3c64xx_spi_port_config exynos5433_spi_port_config = { + .fifo_lvl_mask = { 0x1ff, 0x7f, 0x7f, 0x7f, 0x7f, 0x1ff}, + .rx_lvl_offset = 15, + .tx_st_done = 25, + .high_speed = true, + .clk_from_cmu = true, + .clk_ioclk = true, + .quirks = S3C64XX_SPI_QUIRK_CS_AUTO, +}; + static const struct platform_device_id s3c64xx_spi_driver_ids[] = { { .name = "s3c2443-spi", @@ -1380,6 +1427,9 @@ static const struct of_device_id s3c64xx_spi_dt_match[] = { { .compatible = "samsung,exynos7-spi", .data = (void *)&exynos7_spi_port_config, }, + { .compatible = "samsung,exynos5433-spi", + .data = (void *)&exynos5433_spi_port_config, + }, { }, }; MODULE_DEVICE_TABLE(of, s3c64xx_spi_dt_match); |