diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index e4fa6a2..738b88e 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -18,6 +18,10 @@ bool "Atmel (AT91) SPI Master driver" depends on ARCH_AT91 +config DRIVER_SPI_GPIO + bool "GPIO SPI Master driver" + depends on GPIOLIB + config DRIVER_SPI_IMX bool "i.MX SPI Master driver" depends on ARCH_IMX diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 7469479..7481966 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_SPI) += spi.o obj-$(CONFIG_DRIVER_SPI_ATH79) += ath79_spi.o +obj-$(CONFIG_DRIVER_SPI_GPIO) += gpio_spi.o obj-$(CONFIG_DRIVER_SPI_IMX) += imx_spi.o obj-$(CONFIG_DRIVER_SPI_MVEBU) += mvebu_spi.o obj-$(CONFIG_DRIVER_SPI_MXS) += mxs_spi.o diff --git a/drivers/spi/gpio_spi.c b/drivers/spi/gpio_spi.c new file mode 100644 index 0000000..4ea545a --- /dev/null +++ b/drivers/spi/gpio_spi.c @@ -0,0 +1,240 @@ +/* + * SPI master driver using generic bitbanged GPIO + * + * Sebastian Hesselbarth + * + * Based on Linux driver + * Copyright (C) 2006,2008 David Brownell + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 +#include +#include +#include + +struct gpio_spi { + struct spi_master master; + struct gpio_spi_pdata *data; +}; + +#define priv_from_spi_device(s) container_of(s->master, struct gpio_spi, master) + +static inline void setsck(const struct spi_device *spi, int is_on) +{ + struct gpio_spi *priv = priv_from_spi_device(spi); + gpio_set_value(priv->data->sck, is_on); +} + +static inline void setmosi(const struct spi_device *spi, int is_on) +{ + struct gpio_spi *priv = priv_from_spi_device(spi); + if (!gpio_is_valid(priv->data->mosi)) + return; + gpio_set_value(priv->data->mosi, is_on); +} + +static inline int getmiso(const struct spi_device *spi) +{ + struct gpio_spi *priv = priv_from_spi_device(spi); + if (!gpio_is_valid(priv->data->miso)) + return 1; + return !!gpio_get_value(priv->data->miso); +} + +#define spidelay(nsecs) do { } while (0) + +#include "spi-bitbang-txrx.h" + +static int gpio_spi_set_cs(struct spi_device *spi, bool en) +{ + struct gpio_spi *priv = priv_from_spi_device(spi); + + if (!gpio_is_valid(priv->data->cs[spi->chip_select])) + return 0; + + gpio_set_value(priv->data->cs[spi->chip_select], + (spi->mode & SPI_CS_HIGH) ? en : !en); + + return 0; +} + +static inline u32 gpio_spi_txrx_word(struct spi_device *spi, unsigned nsecs, + u32 word, u8 bits) +{ + int cpol = !!(spi->mode & SPI_CPOL); + if (spi->mode & SPI_CPHA) + return bitbang_txrx_be_cpha1(spi, nsecs, cpol, word, bits); + else + return bitbang_txrx_be_cpha0(spi, nsecs, cpol, word, bits); +} + +static int gpio_spi_transfer_one(struct spi_device *spi, struct spi_transfer *t) +{ + bool read = (t->rx_buf) ? true : false; + u32 word = 0; + int n; + + for (n = 0; n < t->len; n++) { + if (!read) + word = ((const u8 *)t->tx_buf)[n]; + word = gpio_spi_txrx_word(spi, 0, word, 8); + if (read) + ((u8 *)t->rx_buf)[n] = word & 0xff; + } + + return 0; +} + +static int gpio_spi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct spi_transfer *t; + int ret; + + ret = gpio_spi_set_cs(spi, true); + if (ret) + return ret; + + list_for_each_entry(t, &msg->transfers, transfer_list) { + ret = gpio_spi_transfer_one(spi, t); + if (ret) + return ret; + msg->actual_length += t->len; + } + + ret = gpio_spi_set_cs(spi, false); + if (ret) + return ret; + + return 0; +} + +static int gpio_spi_setup(struct spi_device *spi) +{ + if (spi->bits_per_word != 8) { + dev_err(spi->master->dev, "master does not support %d bits per word\n", + spi->bits_per_word); + return -EINVAL; + } + + return 0; +} + +static int gpio_spi_of_probe(struct device_d *dev) +{ + struct device_node *np = dev->device_node; + struct gpio_spi_pdata *pdata; + int n, sck; + + if (!IS_ENABLED(CONFIG_OFDEVICE) || dev->platform_data) + return 0; + + sck = of_get_named_gpio(np, "gpio-sck", 0); + if (sck == -EPROBE_DEFER) + return sck; + if (!gpio_is_valid(sck)) { + dev_err(dev, "missing mandatory SCK gpio\n"); + return sck; + } + + pdata = xzalloc(sizeof(*pdata)); + pdata->sck = sck; + pdata->num_cs = MAX_CHIPSELECT; + + pdata->miso = of_get_named_gpio(np, "gpio-miso", 0); + if (!gpio_is_valid(pdata->miso)) + pdata->miso = SPI_GPIO_NO_MISO; + + pdata->mosi = of_get_named_gpio(np, "gpio-mosi", 0); + if (!gpio_is_valid(pdata->mosi)) + pdata->mosi = SPI_GPIO_NO_MOSI; + + for (n = 0; n < MAX_CHIPSELECT; n++) { + pdata->cs[n] = of_get_named_gpio(np, "cs-gpios", n); + if (!gpio_is_valid(pdata->cs[n])) + pdata->cs[n] = SPI_GPIO_NO_CS; + } + + dev->platform_data = pdata; + + return 0; +} + +static int gpio_spi_probe(struct device_d *dev) +{ + struct gpio_spi *priv; + struct gpio_spi_pdata *pdata; + struct spi_master *master; + int n, ret; + + ret = gpio_spi_of_probe(dev); + if (ret) + return ret; + pdata = dev->platform_data; + + ret = gpio_request_one(pdata->sck, GPIOF_DIR_OUT, "spi-sck"); + if (ret) + return ret; + + if (pdata->miso != SPI_GPIO_NO_MISO) { + ret = gpio_request_one(pdata->miso, GPIOF_DIR_IN, "spi-miso"); + if (ret) + return ret; + } + + if (pdata->mosi != SPI_GPIO_NO_MOSI) { + ret = gpio_request_one(pdata->mosi, GPIOF_DIR_OUT, "spi-mosi"); + if (ret) + return ret; + } + + for (n = 0; n < pdata->num_cs; n++) { + char *cs_name; + + if (!gpio_is_valid(pdata->cs[n])) + continue; + + cs_name = asprintf("spi-cs%d", n); + ret = gpio_request_one(pdata->cs[n], GPIOF_DIR_OUT, cs_name); + if (ret) + return ret; + } + + priv = xzalloc(sizeof(*priv)); + priv->data = pdata; + master = &priv->master; + master->dev = dev; + master->bus_num = dev->id; + master->setup = gpio_spi_setup; + master->transfer = gpio_spi_transfer; + master->num_chipselect = priv->data->num_cs; + + return spi_register_master(&priv->master); +} + +static struct of_device_id __maybe_unused gpio_spi_dt_ids[] = { + { .compatible = "spi-gpio", }, + { } +}; + +static struct driver_d gpio_spi_driver = { + .name = "gpio-spi", + .probe = gpio_spi_probe, + .of_compatible = DRV_OF_COMPAT(gpio_spi_dt_ids), +}; +device_platform_driver(gpio_spi_driver); diff --git a/include/spi/spi_gpio.h b/include/spi/spi_gpio.h new file mode 100644 index 0000000..841c934 --- /dev/null +++ b/include/spi/spi_gpio.h @@ -0,0 +1,36 @@ +/* + * SPI master driver using generic bitbanged GPIO + * + * Sebastian Hesselbarth + * + * Based on Linux driver + * Copyright (C) 2006,2008 David Brownell + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SPI_GPIO_H +#define __SPI_GPIO_H + +#define MAX_CHIPSELECT 4 +#define SPI_GPIO_NO_CS (-1) +#define SPI_GPIO_NO_MISO (-1) +#define SPI_GPIO_NO_MOSI (-1) + +struct gpio_spi_pdata { + int sck; + int mosi; + int miso; + int cs[MAX_CHIPSELECT]; + int num_cs; +}; + +#endif