From: Oleksij Rempel <fixed-term.Oleksij.Rempel@xxxxxxxxxxxx> Add driver for Inter-Chip communication based on SSI32 protocol. SSI32 is Synchronous Serial Interface optimised for 32bit alignment. It is an Transport protocol based on SPI used for industrie devices designed with fallowing points in mind: - implementation with widely available and cheap technology - SPI. - error detection and response within 1 milli seccond. - fixed size messages. - Acknowledgment between applications. Signed-off-by: Oleksij Rempel <linux@xxxxxxxxxxxxxxxx> Signed-off-by: Oleksij Rempel <fixed-term.Oleksij.Rempel@xxxxxxxxxxxx> --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/icc/Kconfig | 13 ++ drivers/icc/Makefile | 5 + drivers/icc/icc_bus.c | 204 ++++++++++++++++++++++++++ drivers/icc/ssi32/Kconfig | 12 ++ drivers/icc/ssi32/Makefile | 6 + drivers/icc/ssi32/ssi32_prot.c | 256 ++++++++++++++++++++++++++++++++ drivers/icc/ssi32/ssi32_prot.h | 94 ++++++++++++ drivers/icc/ssi32/ssi32_spi_hif.c | 300 ++++++++++++++++++++++++++++++++++++++ drivers/icc/ssi32/ssi32_spi_hif.h | 40 +++++ include/linux/icc/icc.h | 88 +++++++++++ include/linux/icc/ssi32.h | 32 ++++ 13 files changed, 1053 insertions(+) create mode 100644 drivers/icc/Kconfig create mode 100644 drivers/icc/Makefile create mode 100644 drivers/icc/icc_bus.c create mode 100644 drivers/icc/ssi32/Kconfig create mode 100644 drivers/icc/ssi32/Makefile create mode 100644 drivers/icc/ssi32/ssi32_prot.c create mode 100644 drivers/icc/ssi32/ssi32_prot.h create mode 100644 drivers/icc/ssi32/ssi32_spi_hif.c create mode 100644 drivers/icc/ssi32/ssi32_spi_hif.h create mode 100644 include/linux/icc/icc.h create mode 100644 include/linux/icc/ssi32.h diff --git a/drivers/Kconfig b/drivers/Kconfig index c0cc96b..acc9b08 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -56,6 +56,8 @@ source "drivers/spmi/Kconfig" source "drivers/hsi/Kconfig" +source "drivers/icc/Kconfig" + source "drivers/pps/Kconfig" source "drivers/ptp/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 527a6da..6656a1e 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_MTD) += mtd/ obj-$(CONFIG_SPI) += spi/ obj-$(CONFIG_SPMI) += spmi/ obj-y += hsi/ +obj-y += icc/ obj-y += net/ obj-$(CONFIG_ATM) += atm/ obj-$(CONFIG_FUSION) += message/ diff --git a/drivers/icc/Kconfig b/drivers/icc/Kconfig new file mode 100644 index 0000000..33b6422 --- /dev/null +++ b/drivers/icc/Kconfig @@ -0,0 +1,13 @@ +# +# ICC driver configuration +# +menuconfig ICC + tristate "ICC support" + ---help--- + The "Inter-Chip Communication" support + +if ICC + +source "drivers/icc/ssi32/Kconfig" + +endif # ICC diff --git a/drivers/icc/Makefile b/drivers/icc/Makefile new file mode 100644 index 0000000..c3662c0 --- /dev/null +++ b/drivers/icc/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for ICC +# +obj-$(CONFIG_ICC) += icc_bus.o +obj-y += ssi32/ diff --git a/drivers/icc/icc_bus.c b/drivers/icc/icc_bus.c new file mode 100644 index 0000000..dceb47c --- /dev/null +++ b/drivers/icc/icc_bus.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2016 Robert Bosch Car Multimedia GmbH + * Authors: + * Oleksij Rempel + * <fixed-term.Oleksij.Rempel@xxxxxxxxxxxx> + * <linux@xxxxxxxxxxxxxxxx> + * + * Licensed under GPLv2 or later. + */ + +#include <linux/compiler.h> +#include <linux/list.h> +#include <linux/kobject.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/icc/icc.h> + +static struct class *icc_class; + +static ssize_t modalias_show(struct device *dev, + struct device_attribute *a __maybe_unused, + char *buf) +{ + return sprintf(buf, "icc:%s\n", dev_name(dev)); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *icc_bus_dev_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(icc_bus_dev); + +static int icc_bus_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + add_uevent_var(env, "MODALIAS=icc:%s", dev_name(dev)); + + return 0; +} + +static int icc_bus_match(struct device *dev, struct device_driver *drv) +{ + if (of_driver_match_device(dev, drv)) + return true; + + if (strcmp(dev_name(dev), drv->name) == 0) + return true; + + return false; +} + +static struct bus_type icc_bus_type = { + .name = "icc", + .dev_groups = icc_bus_dev_groups, + .match = icc_bus_match, + .uevent = icc_bus_uevent, +}; + +static int icc_drv_probe(struct device *dev) +{ + const struct icc_driver *icc_drv = to_icc_driver(dev->driver); + struct icc_device *iccd = to_icc_device(dev); + + return icc_drv->probe(iccd); +} + +static int icc_drv_remove(struct device *dev) +{ + const struct icc_driver *icc_drv = to_icc_driver(dev->driver); + struct icc_device *iccd = to_icc_device(dev); + + return icc_drv->remove(iccd); +} + +static void icc_drv_shutdown(struct device *dev) +{ + const struct icc_driver *icc_drv = to_icc_driver(dev->driver); + struct icc_device *iccd = to_icc_device(dev); + + icc_drv->shutdown(iccd); +} + +int icc_register_driver(struct icc_driver *icc_drv) +{ + icc_drv->driver.bus = &icc_bus_type; + + if (icc_drv->probe) + icc_drv->driver.probe = icc_drv_probe; + if (icc_drv->remove) + icc_drv->driver.remove = icc_drv_remove; + if (icc_drv->shutdown) + icc_drv->driver.shutdown = icc_drv_shutdown; + + return driver_register(&icc_drv->driver); +} +EXPORT_SYMBOL_GPL(icc_register_driver); + +void icc_unregister_driver(struct icc_driver *icc_drv) +{ + return driver_unregister(&icc_drv->driver); +} +EXPORT_SYMBOL_GPL(icc_unregister_driver); + +static void icc_lun_release(struct device *dev) +{ +} + +static int icc_add_lun_from_dt(struct icc_master *iccm, + struct device_node *np) +{ + struct icc_device *iccd; + u32 reg; + int ret; + + ret = of_property_read_u32(np, "reg", ®); + if (ret) { + dev_warn(iccm->dev, "can't parse \"reg\" for %s\n", + np->full_name); + return ret; + } + + if (reg > ICC_MAX_LUNS) { + dev_warn(iccm->dev, "reg is more then %i\n", + ICC_MAX_LUNS); + return -EINVAL; + } + + iccd = &iccm->lun[reg]; + + if (iccd->configured) { + dev_warn(iccm->dev, "lun %i is already configured\n", + reg); + return -EINVAL; + } + + /* TODO: set some usaable name */ + dev_set_name(&iccd->dev, "%s:lun.%i:%s", dev_name(iccm->dev), + reg, np->name); + + iccd->dev.bus = &icc_bus_type; + iccd->dev.parent = iccm->dev; + iccd->dev.release = icc_lun_release; + iccd->dev.of_node = np; + iccd->iccm = iccm; + iccd->lun_number = reg; + + /* TODO use device_create? to move major/minor registration here? */ + ret = device_register(&iccd->dev); + if (ret < 0) { + dev_warn(iccm->dev, "filed to registr lun %i\n", reg); + put_device(&iccd->dev); + } + + iccd->configured = true; + + return 0; +} + +static void icc_add_luns_from_dt(struct icc_master *iccm) +{ + struct device_node *np = iccm->dev->of_node; + struct device_node *child; + + for_each_available_child_of_node(np, child) + icc_add_lun_from_dt(iccm, child); + +} + +void icc_add_luns(struct icc_master *iccm) +{ + iccm->icc_class = icc_class; + icc_add_luns_from_dt(iccm); +} +EXPORT_SYMBOL_GPL(icc_add_luns); + +static int __init icc_bus_init(void) +{ + int ret; + + icc_class = class_create(THIS_MODULE, "icc"); + if (IS_ERR(icc_class)) { + ret = PTR_ERR(icc_class); + return ret; + } + + return bus_register(&icc_bus_type); +} +postcore_initcall(icc_bus_init); + +static void __exit icc_bus_exit(void) +{ + class_destroy(icc_class); + bus_unregister(&icc_bus_type); +} +module_exit(icc_bus_exit); + +MODULE_AUTHOR("Oleksij Rempel <fixed-term.Oleksij.Rempel@xxxxxxxxxxxx>"); +MODULE_DESCRIPTION("ICC framework"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/icc/ssi32/Kconfig b/drivers/icc/ssi32/Kconfig new file mode 100644 index 0000000..e22c322 --- /dev/null +++ b/drivers/icc/ssi32/Kconfig @@ -0,0 +1,12 @@ +# +# Incnet protocol +# + +config ICC_SSI32 + tristate "SSI32 (EXPERIMENTAL)" + depends on ICC + depends on OF + depends on SPI + ---help--- + To compile this driver as a module, choose M here: the module + will be called SSI32. diff --git a/drivers/icc/ssi32/Makefile b/drivers/icc/ssi32/Makefile new file mode 100644 index 0000000..5fdeaed --- /dev/null +++ b/drivers/icc/ssi32/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for SSI32 support code. +# + +icc_ssi32-y := ssi32_spi_hif.o ssi32_prot.o +obj-$(CONFIG_ICC_SSI32) += icc_ssi32.o diff --git a/drivers/icc/ssi32/ssi32_prot.c b/drivers/icc/ssi32/ssi32_prot.c new file mode 100644 index 0000000..31ee5fb --- /dev/null +++ b/drivers/icc/ssi32/ssi32_prot.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2016 Robert Bosch Car Multimedia GmbH + * Authors: + * Oleksij Rempel + * <fixed-term.Oleksij.Rempel@xxxxxxxxxxxx> + * <linux@xxxxxxxxxxxxxxxx> + * + * Licensed under GPLv2 or later. + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/pm_runtime.h> +#include <linux/icc/ssi32.h> +#include <linux/icc/icc.h> + +#include "ssi32_spi_hif.h" + +/* Proper name? alloc? */ +int ssi32_trf_init(struct ssi32_prot_priv *ssi32pp, + struct ssi32_prot_trf *trf, u8 lun, size_t data_size) +{ + struct ssi32_prot_hdr *hdr; + + trf->buf = devm_kzalloc(ssi32pp->dev, ssi32pp->trf_size, + GFP_KERNEL); + if (!trf->buf) + return -ENOMEM; + + trf->hdr = trf->buf; + trf->data = trf->buf + SSI32_HDR_SIZE; + trf->data_size = data_size; + trf->size = data_size + SSI32_HDR_SIZE; + + hdr = trf->hdr; + hdr->lun = lun; + + return 0; +} + +void ssi32_trf_free(struct ssi32_prot_priv *ssi32pp, + struct ssi32_prot_trf *trf) +{ + devm_kfree(ssi32pp->dev, trf->buf); +} + +static unsigned char ssi32_chksum(void *data, size_t len) +{ + u32 sum = 0; + unsigned int i; + u8 *buf = data; + + for (i = 0; i < len; i++) + sum += buf[i]; + /* return 2's complement */ + return (u8)((~sum) + 1); +} + +static void *ssi32_setup_tx_buf(struct ssi32_prot_priv *ssi32pp, + struct ssi32_prot_trf *tx_trf) +{ + struct ssi32_prot_hdr *tx_hdr = tx_trf->hdr; + + if (unlikely(tx_trf->size > ssi32pp->trf_size)) { + dev_warn(ssi32pp->dev, "tx size is to big: %i > %i\n", + tx_trf->size, ssi32pp->trf_size); + return NULL; + } + + tx_hdr->ctrl = SSI32_CTRL_DEFAULT; + tx_hdr->dlc = tx_trf->data_size; + + if (!tx_hdr->dlc) + tx_hdr->ctrl |= SSI32_CTRL_NO_MSG; + else if (tx_trf->data_size < tx_trf->size) + memset(tx_trf->data + tx_trf->data_size, 0, + tx_trf->size - tx_trf->data_size); + + tx_hdr->chk = 0; + tx_hdr->chk = ssi32_chksum(tx_trf->buf, tx_trf->size); + + return tx_trf->buf; +} + +static void ssi32_setup_tx_ack(struct ssi32_prot_ack *ack, u32 err) +{ + memset(ack, 0, sizeof(*ack)); + ack->tx_ack = SSI32_ACK_DEFAULT; + + if (!(err & SSI32_RX_ERR_MASK)) { + ack->tx_ack |= SSI32_ACK_POS_ACK; + return; + } + + if (err & SSI32_RX_CHK_ERR) + ack->tx_ack |= SSI32_ACK_CHK_ERR; + if (err & SSI32_RX_SEQ_ERR) + ack->tx_ack |= SSI32_ACK_SEQ_ERR; + if (err & SSI32_RX_SYN_ERR) + ack->tx_ack |= SSI32_ACK_CHK_ERR; + if (err & SSI32_RX_XOFF_ERR) + ack->tx_ack |= SSI32_ACK_XOFF; +} + +static int ssi32_process_ack(struct ssi32_prot_ack *ack) +{ + return 0; +} + +static u32 ssi32_process_rx(struct ssi32_prot_priv *ssi32pp) +{ + struct ssi32_prot_hdr *rx_hdr = ssi32pp->rx_buf; + struct icc_master *iccm = &ssi32pp->iccm; + struct icc_device *iccd; + size_t rx_size; + int ret; + + rx_size = rx_hdr->dlc + SSI32_HDR_SIZE; + if (rx_size > ssi32pp->trf_size) { + dev_dbg(ssi32pp->dev, "rx size is wrong %i\n", rx_size); + return SSI32_RX_CHK_ERR; + } + + if (ssi32_chksum(ssi32pp->rx_buf, rx_size)) { + dev_dbg(ssi32pp->dev, "rx chksum err\n"); + return SSI32_RX_CHK_ERR; + } + + /* got dummy message */ + if (rx_hdr->lun == SSI32_MAX_LUNS) + return 0; + + iccd = &iccm->lun[rx_hdr->lun]; + if (!iccd->rx_cb) + return SSI32_RX_XOFF_ERR; + + ret = iccd->rx_cb(iccd, ssi32pp->rx_buf + SSI32_HDR_SIZE, + rx_hdr->dlc); + + /* what is better error here? */ + if (ret == -EBUSY) + return SSI32_RX_CHK_ERR; + + return ret; +} + +int ssi32_prot_shift(struct ssi32_prot_priv *ssi32pp, + struct ssi32_prot_trf *tx_trf) +{ + void *tx_buf = NULL; + u32 err = 0; + int ret; + + ssi32_hif_idle(ssi32pp->hif, false); + + if (!tx_trf) + tx_trf = &ssi32pp->tx_dummy_trf; + + if (tx_trf) { + tx_buf = ssi32_setup_tx_buf(ssi32pp, tx_trf); + if (!tx_buf) + return -ENOMEM; + } + + ret = ssi32_shift_one_msg(ssi32pp->hif, tx_buf, + ssi32pp->rx_buf, ssi32pp->trf_size); + if (ret) + return ret; + + err = ssi32_process_rx(ssi32pp); + + ssi32_setup_tx_ack(&ssi32pp->tx_ack, err); + + ret = ssi32_shift_one_msg(ssi32pp->hif, &ssi32pp->tx_ack, + &ssi32pp->rx_ack, SSI32_ACK_SIZE); + if (ret) + return ret; + + ret = ssi32_process_ack(&ssi32pp->rx_ack); + + ssi32_hif_idle(ssi32pp->hif, true); + + return ret; +} + +static int ssi32_trf_alloc(struct icc_master *iccm, + struct icc_trf *tx_trf, + u8 lun, size_t data_size) +{ + struct ssi32_prot_priv *ssi32pp = + container_of(iccm, struct ssi32_prot_priv, iccm); + + /* TODO currently struct icc_trf is identical to struct ssi32_prot_trf */ + return ssi32_trf_init(ssi32pp, (struct ssi32_prot_trf *)tx_trf, lun, data_size); +} + +static int ssi32_trf_xmit(struct icc_master *iccm, + struct icc_trf *tx_trf) +{ + struct ssi32_prot_priv *ssi32pp = + container_of(iccm, struct ssi32_prot_priv, iccm); + int ret; + + mutex_lock(&ssi32pp->lock); + /* currently struct icc_trf is identical to struct ssi32_prot_trf */ + ret = ssi32_prot_shift(ssi32pp, (struct ssi32_prot_trf *)tx_trf); + mutex_unlock(&ssi32pp->lock); + + return ret; +} + +int ssi32_prot_init(struct ssi32_spi_hif_priv *priv) +{ + struct spi_device *spi = priv->spi; + struct ssi32_prot_priv *ssi32pp = &priv->ssi32pp; + struct icc_master *iccm = &ssi32pp->iccm; + int ret; + + iccm->dev = &spi->dev; + ssi32pp->dev = &spi->dev; + ssi32pp->hif = priv; + + mutex_init(&ssi32pp->lock); + + ssi32pp->trf_size = SSI32_MAX_TRANSFER_SIZE; + iccm->max_data_size = ssi32pp->trf_size - SSI32_HDR_SIZE; + ssi32pp->rx_buf = devm_kzalloc(&spi->dev, ssi32pp->trf_size * 2, + GFP_KERNEL); + iccm->trf_xmit = ssi32_trf_xmit; + iccm->trf_alloc = ssi32_trf_alloc; + + ret = ssi32_trf_init(ssi32pp, &ssi32pp->tx_dummy_trf, + SSI32_MAX_LUNS, 0); + if (ret) + return ret; + + icc_add_luns(iccm); + + ssi32_hif_idle(ssi32pp->hif, true); + + return 0; +} + +void ssi32_prot_uninit(struct ssi32_spi_hif_priv *priv) +{ +} diff --git a/drivers/icc/ssi32/ssi32_prot.h b/drivers/icc/ssi32/ssi32_prot.h new file mode 100644 index 0000000..fc39449 --- /dev/null +++ b/drivers/icc/ssi32/ssi32_prot.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 Robert Bosch Car Multimedia GmbH + * Authors: + * Oleksij Rempel + * <fixed-term.Oleksij.Rempel@xxxxxxxxxxxx> + * <linux@xxxxxxxxxxxxxxxx> + * + * Licensed under GPLv2 or later. + */ + +#ifndef SSI32_PROT_H +#define SSI32_PROT_H + +#include <linux/icc/ssi32.h> +#include <linux/icc/icc.h> + +#define SSI32_MAX_TRANSFER_SIZE 128 + +#define SSI32_CTRL_FIXED2 BIT(7) +#define SSI32_CTRL_VER BIT(6) +#define SSI32_CTRL_FIXED1 BIT(5) +/* no message */ +#define SSI32_CTRL_NO_MSG BIT(4) +#define SSI32_CTRL_SN BIT(3) +/* retry message */ +#define SSI32_CTRL_RTR BIT(2) +/* end of message */ +#define SSI32_CTRL_EOM BIT(1) +/* start of message */ +#define SSI32_CTRL_SOM BIT(0) + + +#define SSI32_CTRL_DEFAULT (SSI32_CTRL_VER | SSI32_CTRL_SOM | SSI32_CTRL_EOM) + +#define SSI32_RX_CHK_ERR BIT(3) +#define SSI32_RX_SEQ_ERR BIT(2) +#define SSI32_RX_SYN_ERR BIT(1) +#define SSI32_RX_XOFF_ERR BIT(0) +#define SSI32_RX_ERR_MASK (SSI32_RX_XOFF_ERR | SSI32_RX_SYN_ERR \ + | SSI32_RX_SEQ_ERR | SSI32_RX_CHK_ERR) + +#define SSI32_ACK_RESERVED3 BIT(7) +#define SSI32_ACK_RESERVED2 BIT(6) +#define SSI32_ACK_PARITY BIT(5) +#define SSI32_ACK_POS_ACK BIT(4) +#define SSI32_ACK_XOFF BIT(3) +#define SSI32_ACK_RESERVED1 BIT(2) +#define SSI32_ACK_SEQ_ERR BIT(1) +#define SSI32_ACK_CHK_ERR BIT(0) + +#define SSI32_ACK_DEFAULT (SSI32_ACK_RESERVED2 | SSI32_ACK_RESERVED3) +#define SSI_ACKBLOCK_CHKMASK (0xffffff00 | (SSI32_ACK_RESERVED1 \ + | SSI32_ACK_RESERVED2 | SSI32_ACK_RESERVED3)) + +struct ssi32_prot_test { + struct mutex lock; /* protect from simultaneous accesses */ + struct device *dev; + struct task_struct *task; + wait_queue_head_t wait; + + struct completion input; + atomic_t active_rq; + atomic_t buffs; + struct ssi32_prot_trf trf; + + bool dir_in; + size_t data_size; +}; + +/* ssi32 protocol struct */ +struct ssi32_prot_priv { + struct icc_master iccm; + struct ssi32_spi_hif_priv *hif; + struct mutex lock; /* protect from simultaneous accesses */ + struct device *dev; + struct task_struct *task; + wait_queue_head_t wait; + + struct completion input; + atomic_t active_rq; + + struct ssi32_prot_ack tx_ack; + struct ssi32_prot_ack rx_ack; + + size_t trf_size; + void *rx_buf; + struct ssi32_prot_trf tx_dummy_trf; + + struct ssi32_prot_test test; +}; + +int ssi32_prot_shift(struct ssi32_prot_priv *ssi32pp, + struct ssi32_prot_trf *tx_trf); +#endif /* SSI32_PROT_H */ diff --git a/drivers/icc/ssi32/ssi32_spi_hif.c b/drivers/icc/ssi32/ssi32_spi_hif.c new file mode 100644 index 0000000..33fea82 --- /dev/null +++ b/drivers/icc/ssi32/ssi32_spi_hif.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2016 Robert Bosch Car Multimedia GmbH + * Authors: + * Oleksij Rempel + * <fixed-term.Oleksij.Rempel@xxxxxxxxxxxx> + * <linux@xxxxxxxxxxxxxxxx> + * + * Licensed under GPLv2 or later. + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/pm_runtime.h> + +#include "ssi32_spi_hif.h" + +#define DRIVER_NAME "ssi32_spi_hif" + +static struct dentry *debugfs_root; + +static irqreturn_t spi_fc_rq_thread(int irq, void *dev_id) +{ + struct ssi32_spi_hif_priv *priv = (struct ssi32_spi_hif_priv *)dev_id; + struct ssi32_prot_priv *ssi32pp = &priv->ssi32pp; + + mutex_lock(&ssi32pp->lock); + /* + * slave requeste a transver, it can be processed + * if we wish to TX some thing or do RX only transaction. + */ + if (atomic_read(&priv->active_rq)) + ssi32_prot_shift(ssi32pp, NULL); + mutex_unlock(&ssi32pp->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t spi_fc_rq(int irq, void *dev_id) +{ + struct ssi32_spi_hif_priv *priv = (struct ssi32_spi_hif_priv *)dev_id; + int fc_active; + + fc_active = gpiod_get_value(priv->fc_gpio); + if (atomic_read(&priv->idle) && fc_active) { + atomic_set(&priv->active_rq, 1); + return IRQ_WAKE_THREAD; + } + + complete(&priv->fc_complete); + + return IRQ_HANDLED; +} + +void ssi32_hif_idle(struct ssi32_spi_hif_priv *priv, bool idle) +{ + + atomic_set(&priv->active_rq, !idle); + atomic_set(&priv->idle, idle); + + if (idle && gpiod_get_value(priv->fc_gpio)) { + atomic_set(&priv->active_rq, 1); + irq_wake_thread(priv->fc_irq, priv); + } +} + +/* Wait for flow control */ +static inline int ssi32_hif_wait_fc(struct ssi32_spi_hif_priv *priv, + bool fc_enabled) +{ + int ret, val; + int count = 8; + + /* try to poll the FC line. It will allow us to speed up the response */ + while (count) { + /* + * the switch from SLAVE_FC to SLAVE_REQ can be wary short. + * make sure we didn't missed it. + */ + if (try_wait_for_completion(&priv->fc_complete)) + return 0; + + /* + * Some rounds of polling for SLAVE_FC are still faster then + * wait_for_completion. + */ + val = gpiod_get_value(priv->fc_gpio); + if (val == fc_enabled) + return 0; + count--; + usleep_range(10, 20); + } + + /* now we are getting slow */ + ret = wait_for_completion_interruptible_timeout(&priv->fc_complete, + msecs_to_jiffies(20)); + if (!ret) + return -ETIMEDOUT; + + return 0; +} + +int ssi32_shift_one_msg(struct ssi32_spi_hif_priv *priv, + void *tx_buf, void *rx_buf, size_t len) +{ + struct spi_device *spi = priv->spi; + struct spi_message m; + int ret = 0; + struct spi_transfer t = { + .len = 0, + .cs_change = 1, + }; + + /* prepare dummy message */ + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + spi_bus_lock(spi->master); + + /* send dummy to trigger CS */ + reinit_completion(&priv->fc_complete); + spi_sync_locked(spi, &m); + ret = ssi32_hif_wait_fc(priv, true); + if (ret) { + /* recover the CS line and unlock the bus */ + t.cs_change = 0; + spi_sync_locked(spi, &m); + dev_warn(&spi->dev, "FC failed, not Ready\n"); + spi_bus_unlock(spi->master); + return ret; + } + + /* prepare actual message */ + t.tx_buf = tx_buf; + t.rx_buf = rx_buf; + t.len = len; + t.delay_usecs = 0; + t.cs_change = 0; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + /* + * Using sync variant. according to my test, async is slower: + * sync - 1m2.203s for 100000 packets + * async - 1m14.416s for 100000 packets + */ + reinit_completion(&priv->fc_complete); + spi_sync_locked(spi, &m); + ret = ssi32_hif_wait_fc(priv, false); + if (ret) + dev_warn(&spi->dev, "FC failed, no ACK\n"); + spi_bus_unlock(spi->master); + return ret; +} + +/* send dummy to trigger CS */ +static void ssi32_shift_dummy_msg(struct ssi32_spi_hif_priv *priv) +{ + struct spi_device *spi = priv->spi; + struct spi_message m; + struct spi_transfer t = { + .len = 0, + .cs_change = 0, + }; + + reinit_completion(&priv->fc_complete); + /* prepare dummy message */ + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + spi_bus_lock(spi->master); + + spi_sync_locked(spi, &m); + + spi_bus_unlock(spi->master); +} + +static int ssi32_spi_hif_probe(struct spi_device *spi) +{ + struct ssi32_spi_hif_priv *priv; + int ret; + + spi->bits_per_word = 8; + + ret = spi_setup(spi); + if (ret < 0) + return ret; + + priv = devm_kzalloc(&spi->dev, sizeof(struct ssi32_spi_hif_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->lock); + init_completion(&priv->fc_complete); + init_completion(&priv->input); + init_waitqueue_head(&priv->wait); + + spi_set_drvdata(spi, priv); + + priv->spi = spi; + spi->master->rt = 1; + + priv->fc_gpio = devm_gpiod_get(&spi->dev, "fc", GPIOD_IN); + if (IS_ERR(priv->fc_gpio)) { + ret = PTR_ERR(priv->fc_gpio); + dev_err(&spi->dev, "Failed to request FC GPIO: %d\n", ret); + return ret; + } + + priv->fc_irq = gpiod_to_irq(priv->fc_gpio); + if (priv->fc_irq < 0) { + dev_err(&spi->dev, "Can't registr IRQ for GPIO\n"); + return priv->fc_irq; + } + + ret = devm_request_threaded_irq(&spi->dev, priv->fc_irq, spi_fc_rq, + spi_fc_rq_thread, IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING, + dev_name(&spi->dev), priv); + if (ret) { + dev_err(&spi->dev, "Failed to request FC IRQ\n"); + return ret; + } + + priv->debugfs = debugfs_create_dir(dev_name(&spi->dev), debugfs_root); + + pm_runtime_forbid(spi->master->dev.parent); + /* + * Shift a dummy packet to prepare the SPI Master. For example + * set CS to proper state. FIXME: should it be fixed in SPI framework? + */ + ssi32_shift_dummy_msg(priv); + + return ssi32_prot_init(priv); +} + +static int ssi32_spi_hif_remove(struct spi_device *spi) +{ + struct ssi32_spi_hif_priv *priv = spi_get_drvdata(spi); + + ssi32_prot_uninit(priv); + + pm_runtime_allow(spi->master->dev.parent); + if (!priv) + return -ENODEV; + + mutex_destroy(&priv->lock); + + return 0; +} + +static const struct of_device_id ssi32_spi_hif_match[] = { + { + .compatible = "rbcm,icc-ssi32", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ssi32_spi_hif_match); + +static struct spi_driver ssi32_spi_hif_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(ssi32_spi_hif_match), + }, + .probe = ssi32_spi_hif_probe, + .remove = ssi32_spi_hif_remove, +}; + +static int __init ssi32_spi_hif_init(void) +{ + debugfs_root = debugfs_create_dir(DRIVER_NAME, NULL); + if (IS_ERR_OR_NULL(debugfs_root)) { + pr_err("%s: Filed to create debufs entry.\n", DRIVER_NAME); + return -ENOMEM; + } + + return spi_register_driver(&ssi32_spi_hif_driver); +} +subsys_initcall(ssi32_spi_hif_init); + +static void __exit ssi32_spi_hif_exit(void) +{ + debugfs_remove_recursive(debugfs_root); + spi_unregister_driver(&ssi32_spi_hif_driver); +} +module_exit(ssi32_spi_hif_exit); + +MODULE_AUTHOR("Oleksij Rempel <linux@xxxxxxxxxxxxxxxx>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/icc/ssi32/ssi32_spi_hif.h b/drivers/icc/ssi32/ssi32_spi_hif.h new file mode 100644 index 0000000..bf51a58 --- /dev/null +++ b/drivers/icc/ssi32/ssi32_spi_hif.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 Robert Bosch Car Multimedia GmbH + * Authors: + * Oleksij Rempel + * <fixed-term.Oleksij.Rempel@xxxxxxxxxxxx> + * <linux@xxxxxxxxxxxxxxxx> + * + * Licensed under GPLv2 or later. + */ + +#ifndef SSI32_SPI_HIF_H +#define SSI32_SPI_HIF_H + +#include "ssi32_prot.h" + +struct ssi32_spi_hif_priv { + struct mutex lock; /* protect from simultaneous accesses */ + u8 port_config; + struct spi_device *spi; + struct gpio_desc *fc_gpio; + int fc_irq; + struct dentry *debugfs; + struct completion fc_complete; + struct completion input; + atomic_t active_rq; + atomic_t idle; + struct task_struct *task; + wait_queue_head_t wait; + + struct ssi32_prot_priv ssi32pp; +}; + +void ssi32_hif_idle(struct ssi32_spi_hif_priv *priv, bool idle); +int ssi32_shift_one_msg(struct ssi32_spi_hif_priv *priv, + void *tx_buf, void *rx_buf, size_t len); + +int ssi32_prot_init(struct ssi32_spi_hif_priv *priv); +void ssi32_prot_uninit(struct ssi32_spi_hif_priv *priv); + +#endif /* SSI32_H */ diff --git a/include/linux/icc/icc.h b/include/linux/icc/icc.h new file mode 100644 index 0000000..e0b93e6 --- /dev/null +++ b/include/linux/icc/icc.h @@ -0,0 +1,88 @@ +#ifndef _ICC_CORE_H_ +#define _ICC_CORE_H_ + +#include <linux/device.h> + +#define ICC_MAX_LUNS 255 + +struct icc_trf; +struct icc_master; + +struct icc_device { + struct device dev; + struct icc_master *iccm; + void *data; + bool configured; + unsigned int lun_number; + + int (*rx_cb)(struct icc_device *, void *, size_t); +}; + +struct icc_master { + struct device *dev; + struct class *icc_class; + int max_data_size; + + int (*trf_alloc)(struct icc_master *, struct icc_trf *, u8, size_t); + int (*trf_xmit)(struct icc_master *, struct icc_trf *); + + struct icc_device lun[ICC_MAX_LUNS]; +}; + +struct icc_driver { + struct device_driver driver; + int (*probe)(struct icc_device *); + int (*remove)(struct icc_device *); + void (*shutdown)(struct icc_device *); +}; + +struct icc_trf { + void *buf; + void *hdr; + void *data; + size_t size; + size_t data_size; +}; + +static inline struct icc_device *to_icc_device(struct device *dev) +{ + return dev ? container_of(dev, struct icc_device, dev) : NULL; +} + +static inline struct icc_driver *to_icc_driver(struct device_driver *drv) +{ + return drv ? container_of(drv, struct icc_driver, driver) : NULL; +} + +static inline void icc_set_drvdata(struct icc_device *iccd, void *data) +{ + dev_set_drvdata(&iccd->dev, data); +} + +static inline void *icc_get_drvdata(struct icc_device *iccd) +{ + return dev_get_drvdata(&iccd->dev); +} + +static inline void icc_set_rxcb(struct icc_device *iccd, + int (*rx_cb)(struct icc_device *, void *, size_t)) +{ + iccd->rx_cb = rx_cb; +} + +static inline int icc_trf_alloc(struct icc_master *iccm, + struct icc_trf *trf, u8 lun, size_t size) +{ + return iccm->trf_alloc(iccm, trf, lun, size); +} +static inline int icc_trf_xmit(struct icc_master *iccm, + struct icc_trf *trf) +{ + return iccm->trf_xmit(iccm, trf); +} + +int icc_register_driver(struct icc_driver *); +void icc_unregister_driver(struct icc_driver *); +void icc_add_luns(struct icc_master *iccm); + +#endif /* _ICC_CORE_H_ */ diff --git a/include/linux/icc/ssi32.h b/include/linux/icc/ssi32.h new file mode 100644 index 0000000..8015c21e --- /dev/null +++ b/include/linux/icc/ssi32.h @@ -0,0 +1,32 @@ +#ifndef _SSI32_CORE_H_ +#define _SSI32_CORE_H_ + +#include <linux/device.h> + +#define SSI32_MAX_LUNS 255 + +#define SSI32_HDR_SIZE 4 +struct ssi32_prot_hdr { + u8 ctrl; + u8 lun; + u8 dlc; + u8 chk; +} __packed; + +#define SSI32_ACK_SIZE 4 +struct ssi32_prot_ack { + u8 tx_ack; + u8 reserved0; + u8 reserved1; + u8 reserved2; +} __packed; + +struct ssi32_prot_trf { + void *buf; + struct ssi32_prot_hdr *hdr; + void *data; + size_t size; + size_t data_size; +}; + +#endif /* _SSI32_H_ */ -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-soc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html