From patchwork Sun May 17 16:27:49 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Baryshkov X-Patchwork-Id: 6425031 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 6BA379F1C1 for ; Sun, 17 May 2015 16:36:36 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 29C7C20602 for ; Sun, 17 May 2015 16:36:35 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id CB77A205FF for ; Sun, 17 May 2015 16:36:33 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Yu1VF-0008Ui-D3; Sun, 17 May 2015 16:33:25 +0000 Received: from mail-pd0-x229.google.com ([2607:f8b0:400e:c02::229]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Yu1Ra-0004i8-3C for linux-arm-kernel@lists.infradead.org; Sun, 17 May 2015 16:29:40 +0000 Received: by pdea3 with SMTP id a3so110481075pde.2 for ; Sun, 17 May 2015 09:29:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=sJeSTCFO3AyoUmJ1UUIGGBwCkw1LEC3uTWTJId60AIk=; b=tF/knSulbU+/wm+eWVXLk96zyMWUVbrn+gL5qpu6VHEgn9N+7uELk5dNKWReTxvFbL fCNOknE9nFDkuNulG+Z2G0Q7sRJTNQXROX7xSv55panwkwCMJ8md9jdPzn+tTEDym7/3 2IkZLsRrmmqFrlSdiXmOL9HvWWW3oOpMPXEIEgQFCEIzycAbFj1Jm83IWXZIhsYCl5bM i2hSshVDq/6feyLv4lik4apS0AtYR7g4tzq+iUZgeG4ysI3fMQDazyvTUXzV951swxuR rAwCWANRkn1HFXUnGPc12p1te/JdAZtghAbdgIKbvOBtw6lEQhuiTNRnJBsn04q7+GCx TFGw== X-Received: by 10.70.63.1 with SMTP id c1mr36959637pds.90.1431880157226; Sun, 17 May 2015 09:29:17 -0700 (PDT) Received: from fangorn.rup.mentorg.com (nat-min.mentorg.com. [139.181.32.34]) by mx.google.com with ESMTPSA id pw9sm7574264pac.27.2015.05.17.09.29.11 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sun, 17 May 2015 09:29:16 -0700 (PDT) From: Dmitry Eremin-Solenikov To: Russell King , Daniel Mack , Robert Jarzmik , Linus Walleij , Alexandre Courbot , Wolfram Sang , Dmitry Torokhov , Bryan Wu , Richard Purdie , Samuel Ortiz , Lee Jones , Mark Brown , Jingoo Han , Jean-Christophe Plagniol-Villard , Tomi Valkeinen , Liam Girdwood , Andrea Adami Subject: [PATCH v3 09/17] spi: add locomo SPI driver Date: Sun, 17 May 2015 19:27:49 +0300 Message-Id: <1431880077-26321-10-git-send-email-dbaryshkov@gmail.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1431880077-26321-1-git-send-email-dbaryshkov@gmail.com> References: <1431880077-26321-1-git-send-email-dbaryshkov@gmail.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150517_092938_227215_F505628D X-CRM114-Status: GOOD ( 19.78 ) X-Spam-Score: -0.1 (/) Cc: linux-fbdev@vger.kernel.org, alsa-devel@alsa-project.org, linux-spi@vger.kernel.org, linux-gpio@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.1 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_MED, T_DKIM_INVALID, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP LoCoMo chip has a built-in simple SPI controller. On Sharp SL-5500 PDDAs it is connected to external MMC slot. Signed-off-by: Dmitry Eremin-Solenikov --- drivers/spi/Kconfig | 10 ++ drivers/spi/Makefile | 1 + drivers/spi/spi-locomo.c | 332 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 343 insertions(+) create mode 100644 drivers/spi/spi-locomo.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 198f96b..c9e3176 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -258,6 +258,16 @@ config SPI_LM70_LLP which interfaces to an LM70 temperature sensor using a parallel port. +config SPI_LOCOMO + tristate "Locomo SPI master" + depends on MFD_LOCOMO + help + This enables using the SPI controller as present in the LoCoMo + chips. It is probably only useful on the Sharp SL-5x00 PDA family. + + On SL-5500 and SL-5000 devices this controller is used for + MMC/SD cards. + config SPI_MPC52xx tristate "Freescale MPC52xx SPI (non-PSC) controller support" depends on PPC_MPC52xx diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index d8cbf65..623c463 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_SPI_GPIO) += spi-gpio.o obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o obj-$(CONFIG_SPI_IMX) += spi-imx.o obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o +obj-$(CONFIG_SPI_LOCOMO) += spi-locomo.o obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o diff --git a/drivers/spi/spi-locomo.c b/drivers/spi/spi-locomo.c new file mode 100644 index 0000000..bef0354 --- /dev/null +++ b/drivers/spi/spi-locomo.c @@ -0,0 +1,332 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 + +struct locomospi_dev { + struct regmap *regmap; + + int clock_base; + int clock_div; + unsigned nsecs; + + unsigned int save_ct; + unsigned int save_md; +}; + +static void locomospi_chipselect(struct spi_device *spi, bool enable) +{ + struct locomospi_dev *spidev; + + dev_dbg(&spi->dev, "SPI cs: %s\n", enable ? "enable" : "disable"); + + spidev = spi_master_get_devdata(spi->master); + + regmap_update_bits(spidev->regmap, LOCOMO_SPICT, LOCOMO_SPICT_CS, + enable ? LOCOMO_SPICT_CS : 0); +} + +static u32 locomospi_txrx_word(struct spi_device *spi, + unsigned nsecs, + u32 word) +{ + struct locomospi_dev *spidev; + int wait; + int j; + unsigned int rx; + unsigned int r; + + spidev = spi_master_get_devdata(spi->master); + + if (spidev->clock_div == DIV_64) + wait = 0x10000; + else + wait = 8; + + for (j = 0; j < wait; j++) { + regmap_read(spidev->regmap, LOCOMO_SPIST, &r); + if (r & LOCOMO_SPI_RFW) + break; + } + if (j == wait) + dev_err_ratelimited(&spi->dev, "rfw timeout\n"); + + regmap_write(spidev->regmap, LOCOMO_SPITD, word); + ndelay(nsecs); + + for (j = 0; j < wait; j++) { + regmap_read(spidev->regmap, LOCOMO_SPIST, &r); + if (r & LOCOMO_SPI_RFR) + break; + } + if (j == wait) + dev_err_ratelimited(&spi->dev, "rfr timeout\n"); + + regmap_read(spidev->regmap, LOCOMO_SPIRD, &rx); + ndelay(nsecs); + + dev_dbg(&spi->dev, "SPI txrx: %02x/%02x\n", word, rx); + + return rx; +} + +static void locomo_spi_set_speed(struct locomospi_dev *spidev, u32 hz) +{ + spidev->nsecs = (1000000000/2) / hz; + + if (hz >= 24576000) { + spidev->clock_base = CLOCK_25MHZ; + spidev->clock_div = DIV_1; + } else if (hz >= 22579200) { + spidev->clock_base = CLOCK_22MHZ; + spidev->clock_div = DIV_1; + } else if (hz >= 18432000) { + spidev->clock_base = CLOCK_18MHZ; + spidev->clock_div = DIV_1; + } else if (hz >= 12288000) { + spidev->clock_base = CLOCK_25MHZ; + spidev->clock_div = DIV_2; + } else if (hz >= 11289600) { + spidev->clock_base = CLOCK_22MHZ; + spidev->clock_div = DIV_2; + } else if (hz >= 9216000) { + spidev->clock_base = CLOCK_18MHZ; + spidev->clock_div = DIV_2; + } else if (hz >= 6144000) { + spidev->clock_base = CLOCK_25MHZ; + spidev->clock_div = DIV_4; + } else if (hz >= 5644800) { + spidev->clock_base = CLOCK_22MHZ; + spidev->clock_div = DIV_4; + } else if (hz >= 4608000) { + spidev->clock_base = CLOCK_18MHZ; + spidev->clock_div = DIV_4; + } else if (hz >= 3072000) { + spidev->clock_base = CLOCK_25MHZ; + spidev->clock_div = DIV_8; + } else if (hz >= 2822400) { + spidev->clock_base = CLOCK_22MHZ; + spidev->clock_div = DIV_8; + } else if (hz >= 2304000) { + spidev->clock_base = CLOCK_18MHZ; + spidev->clock_div = DIV_8; + } else if (hz >= 384000) { + spidev->clock_base = CLOCK_25MHZ; + spidev->clock_div = DIV_64; + } else if (hz >= 352800) { + spidev->clock_base = CLOCK_22MHZ; + spidev->clock_div = DIV_64; + } else { /* set to 288 Khz */ + spidev->clock_base = CLOCK_18MHZ; + spidev->clock_div = DIV_64; + } + + regmap_update_bits(spidev->regmap, LOCOMO_SPIMD, + LOCOMO_SPIMD_XSEL | LOCOMO_SPIMD_CLKSEL | + LOCOMO_SPIMD_XEN, + 0); + regmap_update_bits(spidev->regmap, LOCOMO_SPIMD, + LOCOMO_SPIMD_XSEL | LOCOMO_SPIMD_CLKSEL | + LOCOMO_SPIMD_XEN, + spidev->clock_div | (spidev->clock_base << 3) | + LOCOMO_SPIMD_XEN); + + usleep_range(300, 400); +} + +static int locomo_spi_setup_transfer(struct spi_device *spi, + struct spi_transfer *t) +{ + struct locomospi_dev *spidev; + + spidev = spi_master_get_devdata(spi->master); + + regmap_update_bits(spidev->regmap, LOCOMO_SPIMD, + LOCOMO_SPIMD_XON, + LOCOMO_SPIMD_XON); + + locomo_spi_set_speed(spidev, t->speed_hz); + + return 0; +} + +static int locomospi_transfer_one(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *t) +{ + struct locomospi_dev *spidev = spi_master_get_devdata(spi->master); + int rc; + unsigned count; + const u8 *tx = t->tx_buf; + u8 *rx = t->rx_buf; + + rc = locomo_spi_setup_transfer(spi, t); + if (rc < 0) + return rc; + + if (!t->len) + return 0; + + for (count = t->len; likely(count > 0); count--) { + u8 word = 0; + + if (tx) + word = *tx++; + word = locomospi_txrx_word(spi, spidev->nsecs, word); + + if (rx) + *rx++ = word; + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int locomo_spi_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct locomospi_dev *spidev = spi_master_get_devdata(master); + int ret; + + /* Stop the queue running */ + ret = spi_master_suspend(master); + if (ret) { + dev_warn(dev, "cannot suspend master\n"); + return ret; + } + + regmap_read(spidev->regmap, LOCOMO_SPICT, &spidev->save_ct); + regmap_write(spidev->regmap, LOCOMO_SPICT, LOCOMO_SPICT_CS); + regmap_read(spidev->regmap, LOCOMO_SPIMD, &spidev->save_md); + regmap_write(spidev->regmap, LOCOMO_SPIMD, + LOCOMO_SPIMD_MSB1ST | LOCOMO_SPIMD_DOSTAT | + LOCOMO_SPIMD_RCPOL | LOCOMO_SPIMD_TCPOL | + (CLOCK_25MHZ << 3) | DIV_64); + + return 0; +} + +static int locomo_spi_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct locomospi_dev *spidev = spi_master_get_devdata(master); + int ret; + + regmap_write(spidev->regmap, LOCOMO_SPIMD, spidev->save_md); + regmap_write(spidev->regmap, LOCOMO_SPICT, spidev->save_ct); + + /* Start the queue running */ + ret = spi_master_resume(master); + if (ret) + dev_err(dev, "problem starting queue (%d)\n", ret); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(locomo_spi_pm_ops, + locomo_spi_suspend, locomo_spi_resume); + +#define LOCOMO_SPI_PM_OPS (&locomo_spi_pm_ops) +#else +#define LOCOMO_SPI_PM_OPS NULL +#endif + +static int locomo_spi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct locomospi_dev *spidev; + int ret = -ENODEV; + + master = spi_alloc_master(&pdev->dev, sizeof(struct locomospi_dev)); + if (!master) + return -ENOMEM; + + master->bus_num = 0; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 8); + master->max_speed_hz = 24576000; + master->num_chipselect = 1; + master->set_cs = locomospi_chipselect; + master->transfer_one = locomospi_transfer_one; + + spidev = spi_master_get_devdata(master); + + spidev->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!spidev->regmap) + goto out_put; + + spidev->clock_div = DIV_1; + spidev->clock_base = CLOCK_25MHZ; + + platform_set_drvdata(pdev, master); + + regmap_write(spidev->regmap, LOCOMO_SPIMD, + LOCOMO_SPIMD_MSB1ST | LOCOMO_SPIMD_DOSTAT | + LOCOMO_SPIMD_RCPOL | LOCOMO_SPIMD_TCPOL | + LOCOMO_SPIMD_XON); + + regmap_write(spidev->regmap, LOCOMO_SPIMD, + LOCOMO_SPIMD_MSB1ST | LOCOMO_SPIMD_DOSTAT | + LOCOMO_SPIMD_RCPOL | LOCOMO_SPIMD_TCPOL | + LOCOMO_SPIMD_XON | LOCOMO_SPIMD_XEN | + (spidev->clock_base << 3) | spidev->clock_div); + + regmap_write(spidev->regmap, LOCOMO_SPICT, LOCOMO_SPICT_CS | + LOCOMO_SPICT_CEN | LOCOMO_SPICT_RXUEN | + LOCOMO_SPICT_ALIGNEN); + + ret = devm_spi_register_master(&pdev->dev, master); + if (ret) { + dev_err(&pdev->dev, "bitbang start failed with %d\n", ret); + goto out_put; + } + + return 0; + +out_put: + spi_master_put(master); + return ret; +} + +static int locomo_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct locomospi_dev *spidev = spi_master_get_devdata(master); + + regmap_update_bits(spidev->regmap, LOCOMO_SPICT, LOCOMO_SPICT_CEN, 0); + regmap_update_bits(spidev->regmap, LOCOMO_SPIMD, LOCOMO_SPIMD_XEN, 0); + regmap_update_bits(spidev->regmap, LOCOMO_SPIMD, LOCOMO_SPIMD_XON, 0); + regmap_update_bits(spidev->regmap, LOCOMO_SPICT, + LOCOMO_SPICT_CS, + LOCOMO_SPICT_CS); + + return 0; +} + +static struct platform_driver locomo_spi_driver = { + .probe = locomo_spi_probe, + .remove = locomo_spi_remove, + .driver = { + .name = "locomo-spi", + .pm = LOCOMO_SPI_PM_OPS, + }, +}; +module_platform_driver(locomo_spi_driver); + +MODULE_AUTHOR("Thomas Kunze thommy@tabao.de"); +MODULE_DESCRIPTION("LoCoMo SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:locomo-spi");