diff options
-rw-r--r-- | Documentation/devicetree/bindings/spi/ti_qspi.txt | 22 | ||||
-rw-r--r-- | drivers/spi/Kconfig | 8 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/spi-bcm63xx.c | 21 | ||||
-rw-r--r-- | drivers/spi/spi-coldfire-qspi.c | 21 | ||||
-rw-r--r-- | drivers/spi/spi-omap2-mcspi.c | 20 | ||||
-rw-r--r-- | drivers/spi/spi-pl022.c | 21 | ||||
-rw-r--r-- | drivers/spi/spi-pxa2xx.c | 12 | ||||
-rw-r--r-- | drivers/spi/spi-s3c64xx.c | 4 | ||||
-rw-r--r-- | drivers/spi/spi-sh-hspi.c | 18 | ||||
-rw-r--r-- | drivers/spi/spi-tegra114.c | 10 | ||||
-rw-r--r-- | drivers/spi/spi-tegra20-sflash.c | 8 | ||||
-rw-r--r-- | drivers/spi/spi-tegra20-slink.c | 8 | ||||
-rw-r--r-- | drivers/spi/spi-ti-qspi.c | 574 | ||||
-rw-r--r-- | drivers/spi/spi.c | 118 | ||||
-rw-r--r-- | include/linux/spi/spi.h | 27 |
16 files changed, 757 insertions, 136 deletions
diff --git a/Documentation/devicetree/bindings/spi/ti_qspi.txt b/Documentation/devicetree/bindings/spi/ti_qspi.txt new file mode 100644 index 000000000000..1f9641ade0b5 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/ti_qspi.txt @@ -0,0 +1,22 @@ +TI QSPI controller. + +Required properties: +- compatible : should be "ti,dra7xxx-qspi" or "ti,am4372-qspi". +- reg: Should contain QSPI registers location and length. +- #address-cells, #size-cells : Must be present if the device has sub-nodes +- ti,hwmods: Name of the hwmod associated to the QSPI + +Recommended properties: +- spi-max-frequency: Definition as per + Documentation/devicetree/bindings/spi/spi-bus.txt + +Example: + +qspi: qspi@4b300000 { + compatible = "ti,dra7xxx-qspi"; + reg = <0x4b300000 0x100>; + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <25000000>; + ti,hwmods = "qspi"; +}; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 8aad2c1dc33d..f552c89abdd4 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -306,6 +306,14 @@ config SPI_OMAP24XX SPI master controller for OMAP24XX and later Multichannel SPI (McSPI) modules. +config SPI_TI_QSPI + tristate "DRA7xxx QSPI controller support" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + help + QSPI master controller for DRA7xxx used for flash devices. + This device supports single, dual and quad read support, while + it only supports single write mode. + config SPI_OMAP_100K tristate "OMAP SPI 100K" depends on ARCH_OMAP850 || ARCH_OMAP730 || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 985d7bad7932..ab8d8644af0e 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_SPI_OCTEON) += spi-octeon.o obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o obj-$(CONFIG_SPI_OMAP_100K) += spi-omap-100k.o obj-$(CONFIG_SPI_OMAP24XX) += spi-omap2-mcspi.o +obj-$(CONFIG_SPI_TI_QSPI) += spi-ti-qspi.o obj-$(CONFIG_SPI_ORION) += spi-orion.o obj-$(CONFIG_SPI_PL022) += spi-pl022.o obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o diff --git a/drivers/spi/spi-bcm63xx.c b/drivers/spi/spi-bcm63xx.c index de197f72e082..536b0e363826 100644 --- a/drivers/spi/spi-bcm63xx.c +++ b/drivers/spi/spi-bcm63xx.c @@ -231,24 +231,6 @@ static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *first, return 0; } -static int bcm63xx_spi_prepare_transfer(struct spi_master *master) -{ - struct bcm63xx_spi *bs = spi_master_get_devdata(master); - - pm_runtime_get_sync(&bs->pdev->dev); - - return 0; -} - -static int bcm63xx_spi_unprepare_transfer(struct spi_master *master) -{ - struct bcm63xx_spi *bs = spi_master_get_devdata(master); - - pm_runtime_put(&bs->pdev->dev); - - return 0; -} - static int bcm63xx_spi_transfer_one(struct spi_master *master, struct spi_message *m) { @@ -406,11 +388,10 @@ static int bcm63xx_spi_probe(struct platform_device *pdev) master->bus_num = pdata->bus_num; master->num_chipselect = pdata->num_chipselect; - master->prepare_transfer_hardware = bcm63xx_spi_prepare_transfer; - master->unprepare_transfer_hardware = bcm63xx_spi_unprepare_transfer; master->transfer_one_message = bcm63xx_spi_transfer_one; master->mode_bits = MODEBITS; master->bits_per_word_mask = SPI_BPW_MASK(8); + master->auto_runtime_pm = true; bs->msg_type_shift = pdata->msg_type_shift; bs->msg_ctl_width = pdata->msg_ctl_width; bs->tx_io = (u8 *)(bs->regs + bcm63xx_spireg(SPI_MSG_DATA)); diff --git a/drivers/spi/spi-coldfire-qspi.c b/drivers/spi/spi-coldfire-qspi.c index 8e07928cadb3..cc5b75d10c38 100644 --- a/drivers/spi/spi-coldfire-qspi.c +++ b/drivers/spi/spi-coldfire-qspi.c @@ -354,24 +354,6 @@ static int mcfqspi_transfer_one_message(struct spi_master *master, } -static int mcfqspi_prepare_transfer_hw(struct spi_master *master) -{ - struct mcfqspi *mcfqspi = spi_master_get_devdata(master); - - pm_runtime_get_sync(mcfqspi->dev); - - return 0; -} - -static int mcfqspi_unprepare_transfer_hw(struct spi_master *master) -{ - struct mcfqspi *mcfqspi = spi_master_get_devdata(master); - - pm_runtime_put_sync(mcfqspi->dev); - - return 0; -} - static int mcfqspi_setup(struct spi_device *spi) { if (spi->chip_select >= spi->master->num_chipselect) { @@ -473,8 +455,7 @@ static int mcfqspi_probe(struct platform_device *pdev) master->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 16); master->setup = mcfqspi_setup; master->transfer_one_message = mcfqspi_transfer_one_message; - master->prepare_transfer_hardware = mcfqspi_prepare_transfer_hw; - master->unprepare_transfer_hardware = mcfqspi_unprepare_transfer_hw; + master->auto_runtime_pm = true; platform_set_drvdata(pdev, master); diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index fc190a5a0bad..ed4af4708d9a 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -335,23 +335,6 @@ static void omap2_mcspi_restore_ctx(struct omap2_mcspi *mcspi) __raw_writel(cs->chconf0, cs->base + OMAP2_MCSPI_CHCONF0); } -static int omap2_prepare_transfer(struct spi_master *master) -{ - struct omap2_mcspi *mcspi = spi_master_get_devdata(master); - - pm_runtime_get_sync(mcspi->dev); - return 0; -} - -static int omap2_unprepare_transfer(struct spi_master *master) -{ - struct omap2_mcspi *mcspi = spi_master_get_devdata(master); - - pm_runtime_mark_last_busy(mcspi->dev); - pm_runtime_put_autosuspend(mcspi->dev); - return 0; -} - static int mcspi_wait_for_reg_bit(void __iomem *reg, unsigned long bit) { unsigned long timeout; @@ -1318,8 +1301,7 @@ static int omap2_mcspi_probe(struct platform_device *pdev) master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); master->setup = omap2_mcspi_setup; - master->prepare_transfer_hardware = omap2_prepare_transfer; - master->unprepare_transfer_hardware = omap2_unprepare_transfer; + master->auto_runtime_pm = true; master->transfer_one_message = omap2_mcspi_transfer_one_message; master->cleanup = omap2_mcspi_cleanup; master->dev.of_node = node; diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 5a9e05e20bb5..9c511a954d21 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -1555,18 +1555,6 @@ static int pl022_transfer_one_message(struct spi_master *master, return 0; } -static int pl022_prepare_transfer_hardware(struct spi_master *master) -{ - struct pl022 *pl022 = spi_master_get_devdata(master); - - /* - * Just make sure we have all we need to run the transfer by syncing - * with the runtime PM framework. - */ - pm_runtime_get_sync(&pl022->adev->dev); - return 0; -} - static int pl022_unprepare_transfer_hardware(struct spi_master *master) { struct pl022 *pl022 = spi_master_get_devdata(master); @@ -1575,13 +1563,6 @@ static int pl022_unprepare_transfer_hardware(struct spi_master *master) writew((readw(SSP_CR1(pl022->virtbase)) & (~SSP_CR1_MASK_SSE)), SSP_CR1(pl022->virtbase)); - if (pl022->master_info->autosuspend_delay > 0) { - pm_runtime_mark_last_busy(&pl022->adev->dev); - pm_runtime_put_autosuspend(&pl022->adev->dev); - } else { - pm_runtime_put(&pl022->adev->dev); - } - return 0; } @@ -2140,7 +2121,7 @@ static int pl022_probe(struct amba_device *adev, const struct amba_id *id) master->num_chipselect = num_cs; master->cleanup = pl022_cleanup; master->setup = pl022_setup; - master->prepare_transfer_hardware = pl022_prepare_transfer_hardware; + master->auto_runtime_pm = true; master->transfer_one_message = pl022_transfer_one_message; master->unprepare_transfer_hardware = pl022_unprepare_transfer_hardware; master->rt = platform_info->rt; diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c index e0fd6f63c93e..2eb06ee0b326 100644 --- a/drivers/spi/spi-pxa2xx.c +++ b/drivers/spi/spi-pxa2xx.c @@ -811,14 +811,6 @@ static int pxa2xx_spi_transfer_one_message(struct spi_master *master, return 0; } -static int pxa2xx_spi_prepare_transfer(struct spi_master *master) -{ - struct driver_data *drv_data = spi_master_get_devdata(master); - - pm_runtime_get_sync(&drv_data->pdev->dev); - return 0; -} - static int pxa2xx_spi_unprepare_transfer(struct spi_master *master) { struct driver_data *drv_data = spi_master_get_devdata(master); @@ -827,8 +819,6 @@ static int pxa2xx_spi_unprepare_transfer(struct spi_master *master) write_SSCR0(read_SSCR0(drv_data->ioaddr) & ~SSCR0_SSE, drv_data->ioaddr); - pm_runtime_mark_last_busy(&drv_data->pdev->dev); - pm_runtime_put_autosuspend(&drv_data->pdev->dev); return 0; } @@ -1141,8 +1131,8 @@ static int pxa2xx_spi_probe(struct platform_device *pdev) master->cleanup = cleanup; master->setup = setup; master->transfer_one_message = pxa2xx_spi_transfer_one_message; - master->prepare_transfer_hardware = pxa2xx_spi_prepare_transfer; master->unprepare_transfer_hardware = pxa2xx_spi_unprepare_transfer; + master->auto_runtime_pm = true; drv_data->ssp_type = ssp->type; drv_data->null_dma_buf = (u32 *)PTR_ALIGN(&drv_data[1], DMA_ALIGNMENT); diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 2465d6d35b06..c5bc96123c1b 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -356,8 +356,6 @@ static int s3c64xx_spi_prepare_transfer(struct spi_master *spi) while (!is_polling(sdd) && !acquire_dma(sdd)) usleep_range(10000, 11000); - pm_runtime_get_sync(&sdd->pdev->dev); - return 0; } @@ -372,7 +370,6 @@ static int s3c64xx_spi_unprepare_transfer(struct spi_master *spi) sdd->ops->release((enum dma_ch)sdd->tx_dma.ch, &s3c64xx_spi_dma_client); } - pm_runtime_put(&sdd->pdev->dev); return 0; } @@ -1395,6 +1392,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) SPI_BPW_MASK(8); /* the spi->mode bits understood by this driver: */ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + master->auto_runtime_pm = true; sdd->regs = devm_ioremap_resource(&pdev->dev, mem_res); if (IS_ERR(sdd->regs)) { diff --git a/drivers/spi/spi-sh-hspi.c b/drivers/spi/spi-sh-hspi.c index 716edf999538..b95d6a9fb80e 100644 --- a/drivers/spi/spi-sh-hspi.c +++ b/drivers/spi/spi-sh-hspi.c @@ -99,21 +99,6 @@ static int hspi_status_check_timeout(struct hspi_priv *hspi, u32 mask, u32 val) /* * spi master function */ -static int hspi_prepare_transfer(struct spi_master *master) -{ - struct hspi_priv *hspi = spi_master_get_devdata(master); - - pm_runtime_get_sync(hspi->dev); - return 0; -} - -static int hspi_unprepare_transfer(struct spi_master *master) -{ - struct hspi_priv *hspi = spi_master_get_devdata(master); - - pm_runtime_put_sync(hspi->dev); - return 0; -} #define hspi_hw_cs_enable(hspi) hspi_hw_cs_ctrl(hspi, 0) #define hspi_hw_cs_disable(hspi) hspi_hw_cs_ctrl(hspi, 1) @@ -316,9 +301,8 @@ static int hspi_probe(struct platform_device *pdev) master->setup = hspi_setup; master->cleanup = hspi_cleanup; master->mode_bits = SPI_CPOL | SPI_CPHA; - master->prepare_transfer_hardware = hspi_prepare_transfer; + master->auto_runtime_pm = true; master->transfer_one_message = hspi_transfer_one_message; - master->unprepare_transfer_hardware = hspi_unprepare_transfer; ret = spi_register_master(master); if (ret < 0) { dev_err(&pdev->dev, "spi_register_master error.\n"); diff --git a/drivers/spi/spi-tegra114.c b/drivers/spi/spi-tegra114.c index e8f542ab8935..c14e30c8af2e 100644 --- a/drivers/spi/spi-tegra114.c +++ b/drivers/spi/spi-tegra114.c @@ -816,14 +816,6 @@ static int tegra_spi_transfer_one_message(struct spi_master *master, msg->status = 0; msg->actual_length = 0; - ret = pm_runtime_get_sync(tspi->dev); - if (ret < 0) { - dev_err(tspi->dev, "runtime PM get failed: %d\n", ret); - msg->status = ret; - spi_finalize_current_message(master); - return ret; - } - single_xfer = list_is_singular(&msg->transfers); list_for_each_entry(xfer, &msg->transfers, transfer_list) { INIT_COMPLETION(tspi->xfer_completion); @@ -859,7 +851,6 @@ static int tegra_spi_transfer_one_message(struct spi_master *master, ret = 0; exit: tegra_spi_writel(tspi, tspi->def_command1_reg, SPI_COMMAND1); - pm_runtime_put(tspi->dev); msg->status = ret; spi_finalize_current_message(master); return ret; @@ -1053,6 +1044,7 @@ static int tegra_spi_probe(struct platform_device *pdev) master->transfer_one_message = tegra_spi_transfer_one_message; master->num_chipselect = MAX_CHIP_SELECT; master->bus_num = -1; + master->auto_runtime_pm = true; tspi->master = master; tspi->dev = &pdev->dev; diff --git a/drivers/spi/spi-tegra20-sflash.c b/drivers/spi/spi-tegra20-sflash.c index c1d5d95e70ea..1d814dc6e000 100644 --- a/drivers/spi/spi-tegra20-sflash.c +++ b/drivers/spi/spi-tegra20-sflash.c @@ -335,12 +335,6 @@ static int tegra_sflash_transfer_one_message(struct spi_master *master, struct spi_device *spi = msg->spi; int ret; - ret = pm_runtime_get_sync(tsd->dev); - if (ret < 0) { - dev_err(tsd->dev, "pm_runtime_get() failed, err = %d\n", ret); - return ret; - } - msg->status = 0; msg->actual_length = 0; single_xfer = list_is_singular(&msg->transfers); @@ -380,7 +374,6 @@ exit: tegra_sflash_writel(tsd, tsd->def_command_reg, SPI_COMMAND); msg->status = ret; spi_finalize_current_message(master); - pm_runtime_put(tsd->dev); return ret; } @@ -477,6 +470,7 @@ static int tegra_sflash_probe(struct platform_device *pdev) master->mode_bits = SPI_CPOL | SPI_CPHA; master->setup = tegra_sflash_setup; master->transfer_one_message = tegra_sflash_transfer_one_message; + master->auto_runtime_pm = true; master->num_chipselect = MAX_CHIP_SELECT; master->bus_num = -1; diff --git a/drivers/spi/spi-tegra20-slink.c b/drivers/spi/spi-tegra20-slink.c index 80490cc11ce5..c70353672a23 100644 --- a/drivers/spi/spi-tegra20-slink.c +++ b/drivers/spi/spi-tegra20-slink.c @@ -836,11 +836,6 @@ static int tegra_slink_transfer_one_message(struct spi_master *master, msg->status = 0; msg->actual_length = 0; - ret = pm_runtime_get_sync(tspi->dev); - if (ret < 0) { - dev_err(tspi->dev, "runtime get failed: %d\n", ret); - goto done; - } single_xfer = list_is_singular(&msg->transfers); list_for_each_entry(xfer, &msg->transfers, transfer_list) { @@ -878,8 +873,6 @@ static int tegra_slink_transfer_one_message(struct spi_master *master, exit: tegra_slink_writel(tspi, tspi->def_command_reg, SLINK_COMMAND); tegra_slink_writel(tspi, tspi->def_command2_reg, SLINK_COMMAND2); - pm_runtime_put(tspi->dev); -done: msg->status = ret; spi_finalize_current_message(master); return ret; @@ -1086,6 +1079,7 @@ static int tegra_slink_probe(struct platform_device *pdev) master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; master->setup = tegra_slink_setup; master->transfer_one_message = tegra_slink_transfer_one_message; + master->auto_runtime_pm = true; master->num_chipselect = MAX_CHIP_SELECT; master->bus_num = -1; diff --git a/drivers/spi/spi-ti-qspi.c b/drivers/spi/spi-ti-qspi.c new file mode 100644 index 000000000000..e12d962a289f --- /dev/null +++ b/drivers/spi/spi-ti-qspi.c @@ -0,0 +1,574 @@ +/* + * TI QSPI driver + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com + * Author: Sourav Poddar <sourav.poddar@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GPLv2. + * + * 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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/omap-dma.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pinctrl/consumer.h> + +#include <linux/spi/spi.h> + +struct ti_qspi_regs { + u32 clkctrl; +}; + +struct ti_qspi { + struct completion transfer_complete; + + /* IRQ synchronization */ + spinlock_t lock; + + /* list synchronization */ + struct mutex list_lock; + + struct spi_master *master; + void __iomem *base; + struct clk *fclk; + struct device *dev; + + struct ti_qspi_regs ctx_reg; + + u32 spi_max_frequency; + u32 cmd; + u32 dc; + u32 stat; +}; + +#define QSPI_PID (0x0) +#define QSPI_SYSCONFIG (0x10) +#define QSPI_INTR_STATUS_RAW_SET (0x20) +#define QSPI_INTR_STATUS_ENABLED_CLEAR (0x24) +#define QSPI_INTR_ENABLE_SET_REG (0x28) +#define QSPI_INTR_ENABLE_CLEAR_REG (0x2c) +#define QSPI_SPI_CLOCK_CNTRL_REG (0x40) +#define QSPI_SPI_DC_REG (0x44) +#define QSPI_SPI_CMD_REG (0x48) +#define QSPI_SPI_STATUS_REG (0x4c) +#define QSPI_SPI_DATA_REG (0x50) +#define QSPI_SPI_SETUP0_REG (0x54) +#define QSPI_SPI_SWITCH_REG (0x64) +#define QSPI_SPI_SETUP1_REG (0x58) +#define QSPI_SPI_SETUP2_REG (0x5c) +#define QSPI_SPI_SETUP3_REG (0x60) +#define QSPI_SPI_DATA_REG_1 (0x68) +#define QSPI_SPI_DATA_REG_2 (0x6c) +#define QSPI_SPI_DATA_REG_3 (0x70) + +#define QSPI_COMPLETION_TIMEOUT msecs_to_jiffies(2000) + +#define QSPI_FCLK 192000000 + +/* Clock Control */ +#define QSPI_CLK_EN (1 << 31) +#define QSPI_CLK_DIV_MAX 0xffff + +/* Command */ +#define QSPI_EN_CS(n) (n << 28) +#define QSPI_WLEN(n) ((n - 1) << 19) +#define QSPI_3_PIN (1 << 18) +#define QSPI_RD_SNGL (1 << 16) +#define QSPI_WR_SNGL (2 << 16) +#define QSPI_RD_DUAL (3 << 16) +#define QSPI_RD_QUAD (7 << 16) +#define QSPI_INVAL (4 << 16) +#define QSPI_WC_CMD_INT_EN (1 << 14) +#define QSPI_FLEN(n) ((n - 1) << 0) + +/* STATUS REGISTER */ +#define WC 0x02 + +/* INTERRUPT REGISTER */ +#define QSPI_WC_INT_EN (1 << 1) +#define QSPI_WC_INT_DISABLE (1 << 1) + +/* Device Control */ +#define QSPI_DD(m, n) (m << (3 + n * 8)) +#define QSPI_CKPHA(n) (1 << (2 + n * 8)) +#define QSPI_CSPOL(n) (1 << (1 + n * 8)) +#define QSPI_CKPOL(n) (1 << (n * 8)) + +#define QSPI_FRAME 4096 + +#define QSPI_AUTOSUSPEND_TIMEOUT 2000 + +static inline unsigned long ti_qspi_read(struct ti_qspi *qspi, + unsigned long reg) +{ + return readl(qspi->base + reg); +} + +static inline void ti_qspi_write(struct ti_qspi *qspi, + unsigned long val, unsigned long reg) +{ + writel(val, qspi->base + reg); +} + +static int ti_qspi_setup(struct spi_device *spi) +{ + struct ti_qspi *qspi = spi_master_get_devdata(spi->master); + struct ti_qspi_regs *ctx_reg = &qspi->ctx_reg; + int clk_div = 0, ret; + u32 clk_ctrl_reg, clk_rate, clk_mask; + + if (spi->master->busy) { + dev_dbg(qspi->dev, "master busy doing other trasnfers\n"); + return -EBUSY; + } + + if (!qspi->spi_max_frequency) { + dev_err(qspi->dev, "spi max frequency not defined\n"); + return -EINVAL; + } + + clk_rate = clk_get_rate(qspi->fclk); + + clk_div = DIV_ROUND_UP(clk_rate, qspi->spi_max_frequency) - 1; + + if (clk_div < 0) { + dev_dbg(qspi->dev, "clock divider < 0, using /1 divider\n"); + return -EINVAL; + } + + if (clk_div > QSPI_CLK_DIV_MAX) { + dev_dbg(qspi->dev, "clock divider >%d , using /%d divider\n", + QSPI_CLK_DIV_MAX, QSPI_CLK_DIV_MAX + 1); + return -EINVAL; + } + + dev_dbg(qspi->dev, "hz: %d, clock divider %d\n", + qspi->spi_max_frequency, clk_div); + + ret = pm_runtime_get_sync(qspi->dev); + if (ret) { + dev_err(qspi->dev, "pm_runtime_get_sync() failed\n"); + return ret; + } + + clk_ctrl_reg = ti_qspi_read(qspi, QSPI_SPI_CLOCK_CNTRL_REG); + + clk_ctrl_reg &= ~QSPI_CLK_EN; + + /* disable SCLK */ + ti_qspi_write(qspi, clk_ctrl_reg, QSPI_SPI_CLOCK_CNTRL_REG); + + /* enable SCLK */ + clk_mask = QSPI_CLK_EN | clk_div; + ti_qspi_write(qspi, clk_mask, QSPI_SPI_CLOCK_CNTRL_REG); + ctx_reg->clkctrl = clk_mask; + + pm_runtime_mark_last_busy(qspi->dev); + ret = pm_runtime_put_autosuspend(qspi->dev); + if (ret < 0) { + dev_err(qspi->dev, "pm_runtime_put_autosuspend() failed\n"); + return ret; + } + + return 0; +} + +static void ti_qspi_restore_ctx(struct ti_qspi *qspi) +{ + struct ti_qspi_regs *ctx_reg = &qspi->ctx_reg; + + ti_qspi_write(qspi, ctx_reg->clkctrl, QSPI_SPI_CLOCK_CNTRL_REG); +} + +static int qspi_write_msg(struct ti_qspi *qspi, struct spi_transfer *t) +{ + int wlen, count, ret; + unsigned int cmd; + const u8 *txbuf; + + txbuf = t->tx_buf; + cmd = qspi->cmd | QSPI_WR_SNGL; + count = t->len; + wlen = t->bits_per_word; + + while (count) { + switch (wlen) { + case 8: + dev_dbg(qspi->dev, "tx cmd %08x dc %08x data %02x\n", + cmd, qspi->dc, *txbuf); + writeb(*txbuf, qspi->base + QSPI_SPI_DATA_REG); + ti_qspi_write(qspi, cmd, QSPI_SPI_CMD_REG); + ret = wait_for_completion_timeout(&qspi->transfer_complete, + QSPI_COMPLETION_TIMEOUT); + if (ret == 0) { + dev_err(qspi->dev, "write timed out\n"); + return -ETIMEDOUT; + } + txbuf += 1; + count -= 1; + break; + case 16: + dev_dbg(qspi->dev, "tx cmd %08x dc %08x data %04x\n", + cmd, qspi->dc, *txbuf); + writew(*((u16 *)txbuf), qspi->base + QSPI_SPI_DATA_REG); + ti_qspi_write(qspi, cmd, QSPI_SPI_CMD_REG); + ret = wait_for_completion_timeout(&qspi->transfer_complete, + QSPI_COMPLETION_TIMEOUT); + if (ret == 0) { + dev_err(qspi->dev, "write timed out\n"); + return -ETIMEDOUT; + } + txbuf += 2; + count -= 2; + break; + case 32: + dev_dbg(qspi->dev, "tx cmd %08x dc %08x data %08x\n", + cmd, qspi->dc, *txbuf); + writel(*((u32 *)txbuf), qspi->base + QSPI_SPI_DATA_REG); + ti_qspi_write(qspi, cmd, QSPI_SPI_CMD_REG); + ret = wait_for_completion_timeout(&qspi->transfer_complete, + QSPI_COMPLETION_TIMEOUT); + if (ret == 0) { + dev_err(qspi->dev, "write timed out\n"); + return -ETIMEDOUT; + } + txbuf += 4; + count -= 4; + break; + } + } + + return 0; +} + +static int qspi_read_msg(struct ti_qspi *qspi, struct spi_transfer *t) +{ + int wlen, count, ret; + unsigned int cmd; + u8 *rxbuf; + + rxbuf = t->rx_buf; + cmd = qspi->cmd; + switch (t->rx_nbits) { + case SPI_NBITS_DUAL: + cmd |= QSPI_RD_DUAL; + break; + case SPI_NBITS_QUAD: + cmd |= QSPI_RD_QUAD; + break; + default: + cmd |= QSPI_RD_SNGL; + break; + } + count = t->len; + wlen = t->bits_per_word; + + while (count) { + dev_dbg(qspi->dev, "rx cmd %08x dc %08x\n", cmd, qspi->dc); + ti_qspi_write(qspi, cmd, QSPI_SPI_CMD_REG); + ret = wait_for_completion_timeout(&qspi->transfer_complete, + QSPI_COMPLETION_TIMEOUT); + if (ret == 0) { + dev_err(qspi->dev, "read timed out\n"); + return -ETIMEDOUT; + } + switch (wlen) { + case 8: + *rxbuf = readb(qspi->base + QSPI_SPI_DATA_REG); + rxbuf += 1; + count -= 1; + break; + case 16: + *((u16 *)rxbuf) = readw(qspi->base + QSPI_SPI_DATA_REG); + rxbuf += 2; + count -= 2; + break; + case 32: + *((u32 *)rxbuf) = readl(qspi->base + QSPI_SPI_DATA_REG); + rxbuf += 4; + count -= 4; + break; + } + } + + return 0; +} + +static int qspi_transfer_msg(struct ti_qspi *qspi, struct spi_transfer *t) +{ + int ret; + + if (t->tx_buf) { + ret = qspi_write_msg(qspi, t); + if (ret) { + dev_dbg(qspi->dev, "Error while writing\n"); + return ret; + } + } + + if (t->rx_buf) { + ret = qspi_read_msg(qspi, t); + if (ret) { + dev_dbg(qspi->dev, "Error while reading\n"); + return ret; + } + } + + return 0; +} + +static int ti_qspi_start_transfer_one(struct spi_master *master, + struct spi_message *m) +{ + struct ti_qspi *qspi = spi_master_get_devdata(master); + struct spi_device *spi = m->spi; + struct spi_transfer *t; + int status = 0, ret; + int frame_length; + + /* setup device control reg */ + qspi->dc = 0; + + if (spi->mode & SPI_CPHA) + qspi->dc |= QSPI_CKPHA(spi->chip_select); + if (spi->mode & SPI_CPOL) + qspi->dc |= QSPI_CKPOL(spi->chip_select); + if (spi->mode & SPI_CS_HIGH) + qspi->dc |= QSPI_CSPOL(spi->chip_select); + + frame_length = (m->frame_length << 3) / spi->bits_per_word; + + frame_length = clamp(frame_length, 0, QSPI_FRAME); + + /* setup command reg */ + qspi->cmd = 0; + qspi->cmd |= QSPI_EN_CS(spi->chip_select); + qspi->cmd |= QSPI_FLEN(frame_length); + qspi->cmd |= QSPI_WC_CMD_INT_EN; + + ti_qspi_write(qspi, QSPI_WC_INT_EN, QSPI_INTR_ENABLE_SET_REG); + ti_qspi_write(qspi, qspi->dc, QSPI_SPI_DC_REG); + + mutex_lock(&qspi->list_lock); + + list_for_each_entry(t, &m->transfers, transfer_list) { + qspi->cmd |= QSPI_WLEN(t->bits_per_word); + + ret = qspi_transfer_msg(qspi, t); + if (ret) { + dev_dbg(qspi->dev, "transfer message failed\n"); + mutex_unlock(&qspi->list_lock); + return -EINVAL; + } + + m->actual_length += t->len; + } + + mutex_unlock(&qspi->list_lock); + + m->status = status; + spi_finalize_current_message(master); + + ti_qspi_write(qspi, qspi->cmd | QSPI_INVAL, QSPI_SPI_CMD_REG); + + return status; +} + +static irqreturn_t ti_qspi_isr(int irq, void *dev_id) +{ + struct ti_qspi *qspi = dev_id; + u16 int_stat; + + irqreturn_t ret = IRQ_HANDLED; + + spin_lock(&qspi->lock); + + int_stat = ti_qspi_read(qspi, QSPI_INTR_STATUS_ENABLED_CLEAR); + qspi->stat = ti_qspi_read(qspi, QSPI_SPI_STATUS_REG); + + if (!int_stat) { + dev_dbg(qspi->dev, "No IRQ triggered\n"); + ret = IRQ_NONE; + goto out; + } + + ret = IRQ_WAKE_THREAD; + + ti_qspi_write(qspi, QSPI_WC_INT_DISABLE, QSPI_INTR_ENABLE_CLEAR_REG); + ti_qspi_write(qspi, QSPI_WC_INT_DISABLE, + QSPI_INTR_STATUS_ENABLED_CLEAR); + +out: + spin_unlock(&qspi->lock); + + return ret; +} + +static irqreturn_t ti_qspi_threaded_isr(int this_irq, void *dev_id) +{ + struct ti_qspi *qspi = dev_id; + unsigned long flags; + + spin_lock_irqsave(&qspi->lock, flags); + + if (qspi->stat & WC) + complete(&qspi->transfer_complete); + + spin_unlock_irqrestore(&qspi->lock, flags); + + ti_qspi_write(qspi, QSPI_WC_INT_EN, QSPI_INTR_ENABLE_SET_REG); + + return IRQ_HANDLED; +} + +static int ti_qspi_runtime_resume(struct device *dev) +{ + struct ti_qspi *qspi; + struct spi_master *master; + + master = dev_get_drvdata(dev); + qspi = spi_master_get_devdata(master); + ti_qspi_restore_ctx(qspi); + + return 0; +} + +static const struct of_device_id ti_qspi_match[] = { + {.compatible = "ti,dra7xxx-qspi" }, + {.compatible = "ti,am4372-qspi" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ti_qspi_match); + +static int ti_qspi_probe(struct platform_device *pdev) +{ + struct ti_qspi *qspi; + struct spi_master *master; + struct resource *r; + struct device_node *np = pdev->dev.of_node; + u32 max_freq; + int ret = 0, num_cs, irq; + + master = spi_alloc_master(&pdev->dev, sizeof(*qspi)); + if (!master) + return -ENOMEM; + + master->mode_bits = SPI_CPOL | SPI_CPHA; + + master->bus_num = -1; + master->flags = SPI_MASTER_HALF_DUPLEX; + master->setup = ti_qspi_setup; + master->auto_runtime_pm = true; + master->transfer_one_message = ti_qspi_start_transfer_one; + master->dev.of_node = pdev->dev.of_node; + master->bits_per_word_mask = BIT(32 - 1) | BIT(16 - 1) | BIT(8 - 1); + + if (!of_property_read_u32(np, "num-cs", &num_cs)) + master->num_chipselect = num_cs; + + platform_set_drvdata(pdev, master); + + qspi = spi_master_get_devdata(master); + qspi->master = master; + qspi->dev = &pdev->dev; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return irq; + } + + spin_lock_init(&qspi->lock); + mutex_init(&qspi->list_lock); + + qspi->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(qspi->base)) { + ret = PTR_ERR(qspi->base); + goto free_master; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, ti_qspi_isr, + ti_qspi_threaded_isr, 0, + dev_name(&pdev->dev), qspi); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register ISR for IRQ %d\n", + irq); + goto free_master; + } + + qspi->fclk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(qspi->fclk)) { + ret = PTR_ERR(qspi->fclk); + dev_err(&pdev->dev, "could not get clk: %d\n", ret); + } + + init_completion(&qspi->transfer_complete); + + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, QSPI_AUTOSUSPEND_TIMEOUT); + pm_runtime_enable(&pdev->dev); + + if (!of_property_read_u32(np, "spi-max-frequency", &max_freq)) + qspi->spi_max_frequency = max_freq; + + ret = spi_register_master(master); + if (ret) + goto free_master; + + return 0; + +free_master: + spi_master_put(master); + return ret; +} + +static int ti_qspi_remove(struct platform_device *pdev) +{ + struct ti_qspi *qspi = platform_get_drvdata(pdev); + + spi_unregister_master(qspi->master); + + return 0; +} + +static const struct dev_pm_ops ti_qspi_pm_ops = { + .runtime_resume = ti_qspi_runtime_resume, +}; + +static struct platform_driver ti_qspi_driver = { + .probe = ti_qspi_probe, + .remove = ti_qspi_remove, + .driver = { + .name = "ti,dra7xxx-qspi", + .owner = THIS_MODULE, + .pm = &ti_qspi_pm_ops, + .of_match_table = ti_qspi_match, + } +}; + +module_platform_driver(ti_qspi_driver); + +MODULE_AUTHOR("Sourav Poddar <sourav.poddar@ti.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TI QSPI controller driver"); diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 7ed5c147c073..6ef349f82b5f 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -553,6 +553,10 @@ static void spi_pump_messages(struct kthread_work *work) master->unprepare_transfer_hardware(master)) dev_err(&master->dev, "failed to unprepare transfer hardware\n"); + if (master->auto_runtime_pm) { + pm_runtime_mark_last_busy(master->dev.parent); + pm_runtime_put_autosuspend(master->dev.parent); + } return; } @@ -572,11 +576,23 @@ static void spi_pump_messages(struct kthread_work *work) master->busy = true; spin_unlock_irqrestore(&master->queue_lock, flags); + if (!was_busy && master->auto_runtime_pm) { + ret = pm_runtime_get_sync(master->dev.parent); + if (ret < 0) { + dev_err(&master->dev, "Failed to power device: %d\n", + ret); + return; + } + } + if (!was_busy && master->prepare_transfer_hardware) { ret = master->prepare_transfer_hardware(master); if (ret) { dev_err(&master->dev, "failed to prepare transfer hardware\n"); + + if (master->auto_runtime_pm) + pm_runtime_put(master->dev.parent); return; } } @@ -869,6 +885,51 @@ static void of_register_spi_devices(struct spi_master *master) if (of_find_property(nc, "spi-3wire", NULL)) spi->mode |= SPI_3WIRE; + /* Device DUAL/QUAD mode */ + prop = of_get_property(nc, "spi-tx-nbits", &len); + if (!prop || len < sizeof(*prop)) { + dev_err(&master->dev, "%s has no 'spi-tx-nbits' property\n", + nc->full_name); + spi_dev_put(spi); + continue; + } + switch (be32_to_cpup(prop)) { + case SPI_NBITS_SINGLE: + break; + case SPI_NBITS_DUAL: + spi->mode |= SPI_TX_DUAL; + break; + case SPI_NBITS_QUAD: + spi->mode |= SPI_TX_QUAD; + break; + default: + dev_err(&master->dev, "spi-tx-nbits value is not supported\n"); + spi_dev_put(spi); + continue; + } + + prop = of_get_property(nc, "spi-rx-nbits", &len); + if (!prop || len < sizeof(*prop)) { + dev_err(&master->dev, "%s has no 'spi-rx-nbits' property\n", + nc->full_name); + spi_dev_put(spi); + continue; + } + switch (be32_to_cpup(prop)) { + case SPI_NBITS_SINGLE: + break; + case SPI_NBITS_DUAL: + spi->mode |= SPI_RX_DUAL; + break; + case SPI_NBITS_QUAD: + spi->mode |= SPI_RX_QUAD; + break; + default: + dev_err(&master->dev, "spi-rx-nbits value is not supported\n"); + spi_dev_put(spi); + continue; + } + /* Device speed */ prop = of_get_property(nc, "spi-max-frequency", &len); if (!prop || len < sizeof(*prop)) { @@ -1316,6 +1377,19 @@ int spi_setup(struct spi_device *spi) unsigned bad_bits; int status = 0; + /* check mode to prevent that DUAL and QUAD set at the same time + */ + if (((spi->mode & SPI_TX_DUAL) && (spi->mode & SPI_TX_QUAD)) || + ((spi->mode & SPI_RX_DUAL) && (spi->mode & SPI_RX_QUAD))) { + dev_err(&spi->dev, + "setup: can not select dual and quad at the same time\n"); + return -EINVAL; + } + /* if it is SPI_3WIRE mode, DUAL and QUAD should be forbidden + */ + if ((spi->mode & SPI_3WIRE) && (spi->mode & + (SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD))) + return -EINVAL; /* help drivers fail *cleanly* when they need options * that aren't supported with their current master */ @@ -1378,6 +1452,8 @@ static int __spi_async(struct spi_device *spi, struct spi_message *message) /** * Set transfer bits_per_word and max speed as spi device default if * it is not set for this transfer. + * Set transfer tx_nbits and rx_nbits as single transfer default + * (SPI_NBITS_SINGLE) if it is not set for this transfer. */ list_for_each_entry(xfer, &message->transfers, transfer_list) { message->frame_length += xfer->len; @@ -1404,7 +1480,47 @@ static int __spi_async(struct spi_device *spi, struct spi_message *message) return -EINVAL; if (xfer->speed_hz && master->max_speed_hz && xfer->speed_hz > master->max_speed_hz) - return -EINVAL; + + if (xfer->tx_buf && !xfer->tx_nbits) + xfer->tx_nbits = SPI_NBITS_SINGLE; + if (xfer->rx_buf && !xfer->rx_nbits) + xfer->rx_nbits = SPI_NBITS_SINGLE; + /* check transfer tx/rx_nbits: + * 1. keep the value is not out of single, dual and quad + * 2. keep tx/rx_nbits is contained by mode in spi_device + * 3. if SPI_3WIRE, tx/rx_nbits should be in single + */ + if (xfer->tx_buf) { + if (xfer->tx_nbits != SPI_NBITS_SINGLE && + xfer->tx_nbits != SPI_NBITS_DUAL && + xfer->tx_nbits != SPI_NBITS_QUAD) + return -EINVAL; + if ((xfer->tx_nbits == SPI_NBITS_DUAL) && + !(spi->mode & (SPI_TX_DUAL | SPI_TX_QUAD))) + return -EINVAL; + if ((xfer->tx_nbits == SPI_NBITS_QUAD) && + !(spi->mode & SPI_TX_QUAD)) + return -EINVAL; + if ((spi->mode & SPI_3WIRE) && + (xfer->tx_nbits != SPI_NBITS_SINGLE)) + return -EINVAL; + } + /* check transfer rx_nbits */ + if (xfer->rx_buf) { + if (xfer->rx_nbits != SPI_NBITS_SINGLE && + xfer->rx_nbits != SPI_NBITS_DUAL && + xfer->rx_nbits != SPI_NBITS_QUAD) + return -EINVAL; + if ((xfer->rx_nbits == SPI_NBITS_DUAL) && + !(spi->mode & (SPI_RX_DUAL | SPI_RX_QUAD))) + return -EINVAL; + if ((xfer->rx_nbits == SPI_NBITS_QUAD) && + !(spi->mode & SPI_RX_QUAD)) + return -EINVAL; + if ((spi->mode & SPI_3WIRE) && + (xfer->rx_nbits != SPI_NBITS_SINGLE)) + return -EINVAL; + } } message->spi = spi; diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index e1b3e69aeddc..887116dbce2c 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -74,7 +74,7 @@ struct spi_device { struct spi_master *master; u32 max_speed_hz; u8 chip_select; - u8 mode; + u16 mode; #define SPI_CPHA 0x01 /* clock phase */ #define SPI_CPOL 0x02 /* clock polarity */ #define SPI_MODE_0 (0|0) /* (original MicroWire) */ @@ -87,6 +87,10 @@ struct spi_device { #define SPI_LOOP 0x20 /* loopback mode */ #define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */ #define SPI_READY 0x80 /* slave pulls low to pause */ +#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */ +#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */ +#define SPI_RX_DUAL 0x400 /* receive with 2 wires */ +#define SPI_RX_QUAD 0x800 /* receive with 4 wires */ u8 bits_per_word; int irq; void *controller_state; @@ -256,6 +260,9 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv) * @busy: message pump is busy * @running: message pump is running * @rt: whether this queue is set to run as a realtime task + * @auto_runtime_pm: the core should ensure a runtime PM reference is held + * while the hardware is prepared, using the parent + * device for the spidev * @prepare_transfer_hardware: a message will soon arrive from the queue * so the subsystem requests the driver to prepare the transfer hardware * by issuing this call @@ -380,11 +387,13 @@ struct spi_master { bool busy; bool running; bool rt; + bool auto_runtime_pm; int (*prepare_transfer_hardware)(struct spi_master *master); int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg); int (*unprepare_transfer_hardware)(struct spi_master *master); + /* gpio chip select */ int *cs_gpios; }; @@ -454,6 +463,10 @@ extern struct spi_master *spi_busnum_to_master(u16 busnum); * @rx_buf: data to be read (dma-safe memory), or NULL * @tx_dma: DMA address of tx_buf, if @spi_message.is_dma_mapped * @rx_dma: DMA address of rx_buf, if @spi_message.is_dma_mapped + * @tx_nbits: number of bits used for writting. If 0 the default + * (SPI_NBITS_SINGLE) is used. + * @rx_nbits: number of bits used for reading. If 0 the default + * (SPI_NBITS_SINGLE) is used. * @len: size of rx and tx buffers (in bytes) * @speed_hz: Select a speed other than the device default for this * transfer. If 0 the default (from @spi_device) is used. @@ -508,6 +521,11 @@ extern struct spi_master *spi_busnum_to_master(u16 busnum); * by the results of previous messages and where the whole transaction * ends when the chipselect goes intactive. * + * When SPI can transfer in 1x,2x or 4x. It can get this tranfer information + * from device through @tx_nbits and @rx_nbits. In Bi-direction, these + * two should both be set. User can set transfer mode with SPI_NBITS_SINGLE(1x) + * SPI_NBITS_DUAL(2x) and SPI_NBITS_QUAD(4x) to support these three transfer. + * * The code that submits an spi_message (and its spi_transfers) * to the lower layers is responsible for managing its memory. * Zero-initialize every field you don't set up explicitly, to @@ -528,6 +546,11 @@ struct spi_transfer { dma_addr_t rx_dma; unsigned cs_change:1; + u8 tx_nbits; + u8 rx_nbits; +#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */ +#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */ +#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */ u8 bits_per_word; u16 delay_usecs; u32 speed_hz; @@ -876,7 +899,7 @@ struct spi_board_info { /* mode becomes spi_device.mode, and is essential for chips * where the default of SPI_CS_HIGH = 0 is wrong. */ - u8 mode; + u16 mode; /* ... may need additional spi_device chip config data here. * avoid stuff protocol drivers can set; but include stuff |