Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752053AbaLCJRS (ORCPT ); Wed, 3 Dec 2014 04:17:18 -0500 Received: from mail-qa0-f46.google.com ([209.85.216.46]:43477 "EHLO mail-qa0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751868AbaLCJRN (ORCPT ); Wed, 3 Dec 2014 04:17:13 -0500 Date: Wed, 3 Dec 2014 09:17:07 +0000 From: Lee Jones To: Patrice Chotard Cc: linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, broonie@kernel.org, kernel@stlinux.com, linux-spi@vger.kernel.org Subject: Re: [STLinux Kernel] [PATCH v2 1/4] spi: Add new driver for STMicroelectronics' SPI Controller Message-ID: <20141203091707.GH22522@x1> References: <1417534844-25080-1-git-send-email-lee.jones@linaro.org> <1417534844-25080-2-git-send-email-lee.jones@linaro.org> <547DE5FA.5000409@st.com> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <547DE5FA.5000409@st.com> User-Agent: Mutt/1.5.21 (2010-09-15) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org > Some minors remarks below Thanks Patrice. I will fix these up in v3. Mark, Would you mind casting your eye over this submission prior to the v3 please? > On 12/02/2014 04:40 PM, Lee Jones wrote: > >This patch adds support for the SPI portion of ST's SSC device. > > > >Signed-off-by: Lee Jones > >--- > > drivers/spi/Kconfig | 7 + > > drivers/spi/Makefile | 1 + > > drivers/spi/spi-st-ssc4.c | 509 ++++++++++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 517 insertions(+) > > create mode 100644 drivers/spi/spi-st-ssc4.c > > > >diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig > >index 84e7c9e..bd0a10f 100644 > >--- a/drivers/spi/Kconfig > >+++ b/drivers/spi/Kconfig > >@@ -488,6 +488,13 @@ config SPI_SIRF > > help > > SPI driver for CSR SiRFprimaII SoCs > >+config SPI_ST_SSC4 > >+ tristate "STMicroelectronics SPI SSC-based driver" > >+ depends on ARCH_STI || COMPILE_TEST > >+ help > >+ STMicroelectronics SoCs support for SPI. If you say yes to > >+ this option, support will be included for the SSC driven SPI. > >+ > > config SPI_SUN4I > > tristate "Allwinner A10 SoCs SPI controller" > > depends on ARCH_SUNXI || COMPILE_TEST > >diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile > >index 78f24ca..c38e28c 100644 > >--- a/drivers/spi/Makefile > >+++ b/drivers/spi/Makefile > >@@ -74,6 +74,7 @@ obj-$(CONFIG_SPI_SH_HSPI) += spi-sh-hspi.o > > obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o > > obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o > > obj-$(CONFIG_SPI_SIRF) += spi-sirf.o > >+obj-$(CONFIG_SPI_ST_SSC4) += spi-st-ssc4.o > > obj-$(CONFIG_SPI_SUN4I) += spi-sun4i.o > > obj-$(CONFIG_SPI_SUN6I) += spi-sun6i.o > > obj-$(CONFIG_SPI_TEGRA114) += spi-tegra114.o > >diff --git a/drivers/spi/spi-st-ssc4.c b/drivers/spi/spi-st-ssc4.c > >new file mode 100644 > >index 0000000..566d86c > >--- /dev/null > >+++ b/drivers/spi/spi-st-ssc4.c > >@@ -0,0 +1,509 @@ > >+/* > >+ * Copyright (c) 2008-2014 STMicroelectronics Limited > >+ * > >+ * Author: Angus Clark > >+ * Patrice Chotard > >+ * Lee Jones > >+ * > >+ * SPI master mode controller driver, used in STMicroelectronics devices. > >+ * > >+ * May be copied or modified under the terms of the GNU General Public > >+ * License Version 2.0 only. See linux/COPYING for more information. > >+ */ > >+ > >+#include > >+#include > >+#include > >+#include > >+#include > >+#include > >+#include > >+#include > >+#include > >+#include > >+#include > >+#include > >+ > >+/* SSC registers */ > >+#define SSC_BRG 0x000 > >+#define SSC_TBUF 0x004 > >+#define SSC_RBUF 0x008 > >+#define SSC_CTL 0x00C > >+#define SSC_IEN 0x010 > >+#define SSC_I2C 0x018 > >+ > >+/* SSC Control */ > >+#define SSC_CTL_DATA_WIDTH_9 0x8 > >+#define SSC_CTL_DATA_WIDTH_MSK 0xf > >+#define SSC_CTL_BM 0xf > >+#define SSC_CTL_HB BIT(4) > >+#define SSC_CTL_PH BIT(5) > >+#define SSC_CTL_PO BIT(6) > >+#define SSC_CTL_SR BIT(7) > >+#define SSC_CTL_MS BIT(8) > >+#define SSC_CTL_EN BIT(9) > >+#define SSC_CTL_LPB BIT(10) > >+#define SSC_CTL_EN_TX_FIFO BIT(11) > >+#define SSC_CTL_EN_RX_FIFO BIT(12) > >+#define SSC_CTL_EN_CLST_RX BIT(13) > >+ > >+/* SSC Interrupt Enable */ > >+#define SSC_IEN_TEEN BIT(2) > >+ > >+#define FIFO_SIZE 8 > >+ > >+struct spi_st { > >+ /* SSC SPI Controller */ > >+ void __iomem *base; > >+ struct clk *clk; > >+ struct device *dev; > >+ > >+ /* SSC SPI current transaction */ > >+ const u8 *tx_ptr; > >+ u8 *rx_ptr; > >+ u16 bytes_per_word; > >+ unsigned int words_remaining; > >+ unsigned int baud; > >+ struct completion done; > >+}; > >+ > >+static int spi_st_clk_enable(struct spi_st *spi_st) > >+{ > >+ /* > >+ * Current platforms use one of the core clocks for SPI and I2C. > >+ * If we attempt to disable the clock, the system will hang. > >+ * > >+ * TODO: Remove this when platform supports power domains. > >+ */ > >+ return 0; > >+ > >+ return clk_prepare_enable(spi_st->clk); > >+} > >+ > >+static void spi_st_clk_disable(struct spi_st *spi_st) > >+{ > >+ /* > >+ * Current platforms use one of the core clocks for SPI and I2C. > >+ * If we attempt to disable the clock, the system will hang. > >+ * > >+ * TODO: Remove this when platform supports power domains. > >+ */ > >+ return; > >+ > >+ clk_disable_unprepare(spi_st->clk); > >+} > >+ > >+/* Load the TX FIFO */ > >+static void ssc_write_tx_fifo(struct spi_st *spi_st) > >+{ > >+ unsigned int count, i; > >+ uint32_t word = 0; > >+ > >+ if (spi_st->words_remaining > FIFO_SIZE) > >+ count = FIFO_SIZE; > >+ else > >+ count = spi_st->words_remaining; > >+ > >+ for (i = 0; i < count; i++) { > >+ if (spi_st->tx_ptr) { > >+ if (spi_st->bytes_per_word == 1) { > >+ word = *spi_st->tx_ptr++; > >+ } else { > >+ word = *spi_st->tx_ptr++; > >+ word = *spi_st->tx_ptr++ | (word << 8); > >+ } > >+ } > >+ writel_relaxed(word, spi_st->base + SSC_TBUF); > >+ } > >+} > >+ > >+/* Read the RX FIFO */ > >+static void ssc_read_rx_fifo(struct spi_st *spi_st) > >+{ > >+ unsigned int count, i; > >+ uint32_t word = 0; > >+ > >+ if (spi_st->words_remaining > FIFO_SIZE) > >+ count = FIFO_SIZE; > >+ else > >+ count = spi_st->words_remaining; > >+ > >+ for (i = 0; i < count; i++) { > >+ word = readl_relaxed(spi_st->base + SSC_RBUF); > >+ > >+ if (spi_st->rx_ptr) { > >+ if (spi_st->bytes_per_word == 1) { > >+ *spi_st->rx_ptr++ = (uint8_t)word; > >+ } else { > >+ *spi_st->rx_ptr++ = (word >> 8); > >+ *spi_st->rx_ptr++ = word & 0xff; > >+ } > >+ } > >+ } > >+ spi_st->words_remaining -= count; > >+} > >+ > >+static int spi_st_transfer_one(struct spi_master *master, > >+ struct spi_device *spi, struct spi_transfer *t) > >+{ > >+ struct spi_st *spi_st = spi_master_get_devdata(master); > >+ uint32_t ctl = 0; > >+ > >+ /* Setup transfer */ > >+ spi_st->tx_ptr = t->tx_buf; > >+ spi_st->rx_ptr = t->rx_buf; > >+ > >+ if (spi->bits_per_word > 8) { > >+ /* > >+ * Anything greater than 8 bits-per-word requires 2 > >+ * bytes-per-word in the RX/TX buffers > >+ */ > >+ spi_st->bytes_per_word = 2; > >+ spi_st->words_remaining = t->len / 2; > >+ > >+ } else if (spi->bits_per_word == 8 && !(t->len & 0x1)) { > >+ /* > >+ * If transfer is even-length, and 8 bits-per-word, then > >+ * implement as half-length 16 bits-per-word transfer > >+ */ > >+ spi_st->bytes_per_word = 2; > >+ spi_st->words_remaining = t->len/2; > > add space around "/" > > >+ > >+ /* Set SSC_CTL to 16 bits-per-word */ > >+ ctl = readl_relaxed(spi_st->base + SSC_CTL); > >+ writel_relaxed((ctl | 0xf), spi_st->base + SSC_CTL); > >+ > >+ readl_relaxed(spi_st->base + SSC_RBUF); > >+ > >+ } else { > >+ spi_st->bytes_per_word = 1; > >+ spi_st->words_remaining = t->len; > >+ } > >+ > >+ reinit_completion(&spi_st->done); > >+ > >+ /* Start transfer by writing to the TX FIFO */ > >+ ssc_write_tx_fifo(spi_st); > >+ writel_relaxed(SSC_IEN_TEEN, spi_st->base + SSC_IEN); > >+ > >+ /* Wait for transfer to complete */ > >+ wait_for_completion(&spi_st->done); > >+ > >+ /* Restore SSC_CTL if necessary */ > >+ if (ctl) > >+ writel_relaxed(ctl, spi_st->base + SSC_CTL); > >+ > >+ spi_finalize_current_transfer(spi->master); > >+ > >+ return t->len; > >+} > >+ > >+static void spi_st_cleanup(struct spi_device *spi) > >+{ > >+ int cs = spi->cs_gpio; > >+ > >+ if (gpio_is_valid(cs)) > >+ devm_gpio_free(&spi->dev, cs); > >+} > >+ > >+/* the spi->mode bits understood by this driver: */ > >+#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | SPI_LOOP | SPI_CS_HIGH) > >+static int spi_st_setup(struct spi_device *spi) > >+{ > >+ struct spi_st *spi_st = spi_master_get_devdata(spi->master); > >+ u32 spi_st_clk, sscbrg, var; > >+ u32 hz = spi->max_speed_hz; > >+ int cs = spi->cs_gpio; > >+ int ret; > >+ > >+ if (spi->mode & ~MODEBITS) { > >+ dev_err(&spi->dev, "unsupported mode bits 0x%x\n", > >+ spi->mode & ~MODEBITS); > >+ return -EINVAL; > >+ } > >+ > >+ if (!hz) { > >+ dev_err(&spi->dev, "max_speed_hz unspecified\n"); > >+ return -EINVAL; > >+ } > >+ > >+ if (!gpio_is_valid(cs)) { > >+ dev_err(&spi->dev, "%d is not a valid gpio\n", cs); > >+ return -EINVAL; > >+ } > >+ > >+ if (devm_gpio_request(&spi->dev, cs, dev_name(&spi->dev))) { > >+ dev_err(&spi->dev, "could not request gpio:%d\n", cs); > >+ return -EINVAL; > >+ } > >+ > >+ ret = gpio_direction_output(cs, spi->mode & SPI_CS_HIGH); > >+ if (ret) > >+ return ret; > >+ > >+ spi_st_clk = clk_get_rate(spi_st->clk); > >+ > >+ /* Set SSC_BRF */ > >+ sscbrg = spi_st_clk / (2 * hz); > >+ if (sscbrg < 0x07 || sscbrg > (0x1 << 16)) { > > (0x1 << 16) can be replaced by BIT(16) > > > > >+ dev_err(&spi->dev, > >+ "baudrate %d outside valid range %d\n", sscbrg, hz); > >+ return -EINVAL; > >+ } > >+ > >+ spi_st->baud = spi_st_clk / (2 * sscbrg); > >+ if (sscbrg == (0x1 << 16)) /* 16-bit counter wraps */ > > ditto > > >+ sscbrg = 0x0; > >+ > >+ writel_relaxed(sscbrg, spi_st->base + SSC_BRG); > >+ > >+ dev_dbg(&spi->dev, > >+ "setting baudrate:target= %u hz, actual= %u hz, sscbrg= %u\n", > >+ hz, spi_st->baud, sscbrg); > >+ > >+ /* Set SSC_CTL and enable SSC */ > >+ var = readl_relaxed(spi_st->base + SSC_CTL); > >+ var |= SSC_CTL_MS; > >+ > >+ if (spi->mode & SPI_CPOL) > >+ var |= SSC_CTL_PO; > >+ else > >+ var &= ~SSC_CTL_PO; > >+ > >+ if (spi->mode & SPI_CPHA) > >+ var |= SSC_CTL_PH; > >+ else > >+ var &= ~SSC_CTL_PH; > >+ > >+ if ((spi->mode & SPI_LSB_FIRST) == 0) > >+ var |= SSC_CTL_HB; > >+ else > >+ var &= ~SSC_CTL_HB; > >+ > >+ if (spi->mode & SPI_LOOP) > >+ var |= SSC_CTL_LPB; > >+ else > >+ var &= ~SSC_CTL_LPB; > >+ > >+ var &= ~SSC_CTL_DATA_WIDTH_MSK; > >+ var |= (spi->bits_per_word - 1); > >+ > >+ var |= SSC_CTL_EN_TX_FIFO | SSC_CTL_EN_RX_FIFO; > >+ var |= SSC_CTL_EN; > >+ > >+ writel_relaxed(var, spi_st->base + SSC_CTL); > >+ > >+ /* Clear the status register */ > >+ readl_relaxed(spi_st->base + SSC_RBUF); > >+ > >+ return 0; > >+} > >+ > >+/* Interrupt fired when TX shift register becomes empty */ > >+static irqreturn_t spi_st_irq(int irq, void *dev_id) > >+{ > >+ struct spi_st *spi_st = (struct spi_st *)dev_id; > >+ > >+ /* Read RX FIFO */ > >+ ssc_read_rx_fifo(spi_st); > >+ > >+ /* Fill TX FIFO */ > >+ if (spi_st->words_remaining) { > >+ ssc_write_tx_fifo(spi_st); > >+ } else { > >+ /* TX/RX complete */ > >+ writel_relaxed(0x0, spi_st->base + SSC_IEN); > >+ /* > >+ * read SSC_IEN to ensure that this bit is set > >+ * before re-enabling interrupt > >+ */ > >+ readl(spi_st->base + SSC_IEN); > >+ complete(&spi_st->done); > >+ } > >+ > >+ return IRQ_HANDLED; > >+} > >+ > >+static int spi_st_probe(struct platform_device *pdev) > >+{ > >+ struct device_node *np = pdev->dev.of_node; > >+ struct spi_master *master; > >+ struct resource *res; > >+ struct spi_st *spi_st; > >+ int irq, ret = 0; > >+ u32 var; > >+ > >+ master = spi_alloc_master(&pdev->dev, sizeof(*spi_st)); > >+ if (!master) > >+ return -ENOMEM; > >+ > >+ master->dev.of_node = np; > >+ master->mode_bits = MODEBITS; > >+ master->setup = spi_st_setup; > >+ master->cleanup = spi_st_cleanup; > >+ master->transfer_one = spi_st_transfer_one; > >+ master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); > >+ master->auto_runtime_pm = true; > >+ master->bus_num = pdev->id; > >+ spi_st = spi_master_get_devdata(master); > >+ > >+ spi_st->clk = devm_clk_get(&pdev->dev, "ssc"); > >+ if (IS_ERR(spi_st->clk)) { > >+ dev_err(&pdev->dev, "Unable to request clock\n"); > >+ return PTR_ERR(spi_st->clk); > >+ } > >+ > >+ ret = spi_st_clk_enable(spi_st); > >+ if (ret) > >+ return ret; > >+ > >+ init_completion(&spi_st->done); > >+ > >+ /* Get resources */ > >+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > >+ spi_st->base = devm_ioremap_resource(&pdev->dev, res); > >+ if (IS_ERR(spi_st->base)) { > >+ ret = PTR_ERR(spi_st->base); > >+ goto clk_disable; > >+ } > >+ > >+ /* Disable I2C and Reset SSC */ > >+ writel_relaxed(0x0, spi_st->base + SSC_I2C); > >+ var = readw_relaxed(spi_st->base + SSC_CTL); > >+ var |= SSC_CTL_SR; > >+ writel_relaxed(var, spi_st->base + SSC_CTL); > >+ > >+ udelay(1); > >+ var = readl_relaxed(spi_st->base + SSC_CTL); > >+ var &= ~SSC_CTL_SR; > >+ writel_relaxed(var, spi_st->base + SSC_CTL); > >+ > >+ /* Set SSC into slave mode before reconfiguring PIO pins */ > >+ var = readl_relaxed(spi_st->base + SSC_CTL); > >+ var &= ~SSC_CTL_MS; > >+ writel_relaxed(var, spi_st->base + SSC_CTL); > >+ > >+ irq = irq_of_parse_and_map(np, 0); > >+ if (!irq) { > >+ dev_err(&pdev->dev, "IRQ missing or invalid\n"); > >+ ret = -EINVAL; > >+ goto clk_disable; > >+ } > >+ > >+ ret = devm_request_irq(&pdev->dev, irq, spi_st_irq, 0, > >+ pdev->name, spi_st); > >+ if (ret) { > >+ dev_err(&pdev->dev, "Failed to request irq %d\n", irq); > >+ goto clk_disable; > >+ } > >+ > >+ /* by default the device is on */ > >+ pm_runtime_set_active(&pdev->dev); > >+ pm_runtime_enable(&pdev->dev); > >+ > >+ platform_set_drvdata(pdev, master); > >+ > >+ ret = devm_spi_register_master(&pdev->dev, master); > >+ if (ret) { > >+ dev_err(&pdev->dev, "Failed to register master\n"); > >+ goto clk_disable; > >+ } > >+ > >+ return 0; > >+ > >+clk_disable: > >+ spi_st_clk_disable(spi_st); > >+ > >+ return ret; > >+} > >+ > >+static int spi_st_remove(struct platform_device *pdev) > >+{ > >+ struct spi_master *master = platform_get_drvdata(pdev); > >+ struct spi_st *spi_st = spi_master_get_devdata(master); > >+ > >+ spi_st_clk_disable(spi_st); > >+ > >+ pinctrl_pm_select_sleep_state(&pdev->dev); > >+ > >+ return 0; > >+} > >+ > >+#ifdef CONFIG_PM > >+static int spi_st_runtime_suspend(struct device *dev) > >+{ > >+ struct spi_master *master = dev_get_drvdata(dev); > >+ struct spi_st *spi_st = spi_master_get_devdata(master); > >+ > >+ writel_relaxed(0, spi_st->base + SSC_IEN); > >+ pinctrl_pm_select_sleep_state(dev); > >+ > >+ spi_st_clk_disable(spi_st); > >+ > >+ return 0; > >+} > >+ > >+static int spi_st_runtime_resume(struct device *dev) > >+{ > >+ struct spi_master *master = dev_get_drvdata(dev); > >+ struct spi_st *spi_st = spi_master_get_devdata(master); > >+ int ret; > >+ > >+ ret = spi_st_clk_enable(spi_st); > >+ pinctrl_pm_select_default_state(dev); > >+ > >+ return ret; > >+} > >+#endif > >+ > >+#ifdef CONFIG_PM_SLEEP > >+static int spi_st_suspend(struct device *dev) > >+{ > >+ struct spi_master *master = dev_get_drvdata(dev); > >+ int ret; > >+ > >+ ret = spi_master_suspend(master); > >+ if (ret) > >+ return ret; > >+ > >+ return pm_runtime_force_suspend(dev); > >+} > >+ > >+static int spi_st_resume(struct device *dev) > >+{ > >+ struct spi_master *master = dev_get_drvdata(dev); > >+ int ret; > >+ > >+ ret = spi_master_resume(master); > >+ if (ret) > >+ return ret; > >+ > >+ return pm_runtime_force_resume(dev); > >+} > >+#endif > >+ > >+static const struct dev_pm_ops spi_st_pm = { > >+ SET_SYSTEM_SLEEP_PM_OPS(spi_st_suspend, spi_st_resume) > >+ SET_RUNTIME_PM_OPS(spi_st_runtime_suspend, spi_st_runtime_resume, NULL) > >+}; > >+ > >+static struct of_device_id stm_spi_match[] = { > >+ { .compatible = "st,comms-ssc4-spi", }, > >+ {}, > >+}; > >+MODULE_DEVICE_TABLE(of, stm_spi_match); > >+ > >+static struct platform_driver spi_st_driver = { > >+ .driver = { > >+ .name = "spi-st", > >+ .pm = &spi_st_pm, > >+ .of_match_table = of_match_ptr(stm_spi_match), > >+ }, > >+ .probe = spi_st_probe, > >+ .remove = spi_st_remove, > >+}; > >+module_platform_driver(spi_st_driver); > >+ > >+MODULE_AUTHOR("Patrice Chotard "); > >+MODULE_DESCRIPTION("STM SSC SPI driver"); > >+MODULE_LICENSE("GPL v2"); > -- Lee Jones Linaro STMicroelectronics Landing Team Lead Linaro.org │ Open source software for ARM SoCs Follow Linaro: Facebook | Twitter | Blog -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/