|
|
@@ -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);
|