spi: Add SPI GPIO bitbang driver
This adds a driver for SPI master by GPIO pins. Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
This commit is contained in:
parent
e1eb436242
commit
301765846b
|
@ -18,6 +18,10 @@ config DRIVER_SPI_ATMEL
|
|||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* SPI master driver using generic bitbanged GPIO
|
||||
*
|
||||
* Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
|
||||
*
|
||||
* 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 <common.h>
|
||||
#include <driver.h>
|
||||
#include <errno.h>
|
||||
#include <gpio.h>
|
||||
#include <init.h>
|
||||
#include <io.h>
|
||||
#include <malloc.h>
|
||||
#include <of_gpio.h>
|
||||
#include <spi/spi.h>
|
||||
#include <spi/spi_gpio.h>
|
||||
|
||||
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);
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* SPI master driver using generic bitbanged GPIO
|
||||
*
|
||||
* Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
|
||||
*
|
||||
* 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
|
Loading…
Reference in New Issue