From 5e8ee87e0f4e6d9a713cd126acedaaa5c17b3f66 Mon Sep 17 00:00:00 2001 From: shivashankar thati Date: Thu, 12 Aug 2021 19:43:59 +0530 Subject: [PATCH] Ad7606b: Added the driver code for ad7606b Added driver config for ad7606b Added the entry for ad7606b and gpio support in dts file Added the spi_engine_write_reg function for register read/write in ad7606b Signed-off-by: shivashankar thati --- ...a_cyclone5_tei0022_02_axi_hdmi_ad7606b.dts | 377 ++++ .../arm/configs/socfpga_tei0022_adi_defconfig | 3 + drivers/iio/adc/ad7606.c | 284 +-- drivers/iio/adc/ad7606.h | 16 +- drivers/iio/adc/ad7606_par.c | 300 +++- drivers/iio/adc/ad7606_spi.c | 144 +- drivers/spi/spi-axi-spi-engine.c | 1580 +++++++++-------- include/linux/spi/spi-engine.h | 5 + 8 files changed, 1738 insertions(+), 971 deletions(-) create mode 100755 arch/arm/boot/dts/socfpga_cyclone5_tei0022_02_axi_hdmi_ad7606b.dts diff --git a/arch/arm/boot/dts/socfpga_cyclone5_tei0022_02_axi_hdmi_ad7606b.dts b/arch/arm/boot/dts/socfpga_cyclone5_tei0022_02_axi_hdmi_ad7606b.dts new file mode 100755 index 000000000000..e2013ff0ee13 --- /dev/null +++ b/arch/arm/boot/dts/socfpga_cyclone5_tei0022_02_axi_hdmi_ad7606b.dts @@ -0,0 +1,377 @@ + +#include "socfpga_cyclone5.dtsi" +#include "dt-bindings/interrupt-controller/irq.h" +#include + +/ { + model = "Trenz tei0022"; + compatible = "altr,socfpga-cyclone5", "altr,socfpga"; + + chosen { + bootargs = "earlyprintk"; + stdout-path = "serial0:115200n8"; + }; + + memory { + name = "memory"; + device_type = "memory"; + reg = <0x0 0x80000000>; + }; + + aliases { + ethernet0 = &gmac1; + }; + + dma_clk: dma_clk { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + clock-frequency = <100000000>; + clock-output-names = "dma_clock"; + }; + + ad7606_mclk: ad7606_mclk { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <100000000>; + clock-output-names = "ad7606_mclk"; + }; + + spi_engine_clk: spi_engine_clk { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + clock-frequency = <100000000>; + clock-output-names = "spi_engine_clock"; + }; + + spi_clk: spi_clk { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + clock-frequency = <100000000>; + clock-output-names = "spi_clock"; + }; + + + sys_clk: sys_clk { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + clock-frequency = <100000000>; + clock-output-names = "sys_clock"; + }; + + hdmi_pll: hdmi_pll { + compatible = "altr,altera_iopll-18.1"; + #clock-cells = <1>; + hdmi_pll_outclk0: hdmi_pll_outclk0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <148500000>; + clock-output-names = "hdmi_pll-outclk0"; + }; + }; + + /* add additional system clock device tree entries here */ + + vdd: regulator-vdd { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + vdd_3_3: regulator-vdd { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + vref: regulator-vref { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; + + + soc { + clkmgr@ffd04000 { + clocks { + osc1 { + clock-frequency = <25000000>; + }; + }; + }; + + gmac1: ethernet@ff702000 { + + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + phy-mode = "rgmii-id"; + + ethernet-phy@1 { + reg = <1>; + adi,rx-internal-delay-ps = <2000>; + adi,tx-internal-delay-ps = <2000>; + }; + + }; + + timer0: timer0@ffc08000 { + clock-frequency = <100000000>; + }; + + timer1: timer1@ffc09000 { + clock-frequency = <100000000>; + }; + + timer2: timer2@ffd00000 { + clock-frequency = <25000000>; + }; + + timer3: timer3@ffd01000 { + clock-frequency = <25000000>; + }; + + uart0: serial0@ffc02000 { + clock-frequency = <100000000>; + }; + + uart1: serial1@ffc03000 { + clock-frequency = <100000000>; + }; + + mmc: dwmmc0@ff704000 { + status = "okay"; + supports-highspeed; + altr,dw-mshc-ciu-div = <0x3>; + altr,dw-mshc-sdr-timing = <0x0 0x3>; + slot@0 { + reg = <0x0>; + bus-width = <0x4>; + }; + }; + + usb1: usb@ffb40000 { + status = "okay"; + enable-dynamic-fifo = <1>; + host-rx-fifo-size = <0xa00>; + host-perio-tx-fifo-size = <0xa00>; + host-nperio-tx-fifo-size = <0xa00>; + dma-desc-enable = <0>; + dr_mode = "host"; + }; + + i2c0: i2c@ffc04000 { + status = "okay"; + speed-mode = <0>; + #address-cells = <1>; + #size-cells = <0>; + }; + + i2c1: i2c@ffc05000 { + status = "okay"; + speed-mode = <0>; + #address-cells = <1>; + #size-cells = <0>; + + /* adv7511 device tree entry. for additional information see /linux/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt */ + + adv7511: adv7511@39 { + compatible = "adi,adv7511"; + reg = <0x39>, <0x3f>; + reg-names = "primary", "edid"; + + adi,input-depth = <8>; + adi,input-colorspace = "yuv422"; + adi,input-clock = "1x"; + adi,input-style = <1>; + adi,input-justification = "right"; + adi,clock-delay = <(0)>; + + avdd-supply = <&vdd>; + dvdd-supply = <&vdd>; + pvdd-supply = <&vdd>; + dvdd-3v-supply = <&vdd_3_3>; + bgvdd-supply = <&vdd>; + + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + adv7511_in: endpoint { + remote-endpoint = <&axi_hdmi_out>; + }; + }; + + port@1 { + reg = <1>; + + }; + + }; + }; + }; + + /* add additional hps peripheral device tree entries here */ + + + sys_hps_bridges: bridge@ff200000 { + compatible = "simple-bus"; + reg = <0xff200000 0x00200000>; + reg-names ="axi_h2f_lw"; + #address-cells = <2>; + #size-cells = <1>; + ranges = <0x00000001 0x00010000 0xff210000 0x00000008>, + <0x00000001 0x00020060 0xff220060 0x00000080>, + <0x00000001 0x00020080 0xff220080 0x00000080>, + <0x00000001 0x00030000 0xff230000 0x00000800>, + <0x00000001 0x00038000 0xff238000 0x00000800>, + <0x00000001 0x00040000 0xff240000 0x00010000>, + <0x00000001 0x00050000 0xff250000 0x00000800>, + <0x00000001 0x00060000 0xff260000 0x00010000>, + <0x00000001 0x00070000 0xff270000 0x00000800>, + <0x00000001 0x00090000 0xff290000 0x00000800>, + <0x00000001 0x000a0000 0xff2a0000 0x00010000>; + + sys_id: sys-id@100010000 { + compatible = "altr,sysid-1.0"; + reg = <0x00000001 0x00010000 0x08>; + status = "okay"; + }; + + gpio_altr: gpio_altr@100020060 { + compatible = "altr,pio-1.0"; + //reg = <0xff220050 0x10>; + reg = <0x00000001 0x00020060 0x08>; + //interrupts = <0 47 4>; + //altr,ngpio = <32>; + altr,ngpio = <16>; + //altr,interrupt-type = ; + #gpio-cells = <2>; + //gpio-controller; + //interrupt-controller; + //#interrupt-cells = <1>; + }; + + hdmi_dma: hdmi-dma@100090000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x00000001 0x00090000 0x0800>; + #dma-cells = <1>; + interrupt-parent = <&intc>; + interrupts = <0 44 4>; + clocks = <&dma_clk 0>; + status = "okay"; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma-channel@0 { + reg = <0>; + adi,source-bus-width = <64>; + adi,source-bus-type = <0>; + adi,destination-bus-width = <64>; + adi,destination-bus-type = <1>; + }; + }; + }; + + + axi_hdmi: axi-hdmi@1000a0000 { + compatible = "adi,axi-hdmi-tx-1.00.a"; + reg = <0x00000001 0x000a0000 0x10000>; + dmas = <&hdmi_dma 0>; + dma-names = "video"; + clocks = <&hdmi_pll 0>; + status = "okay"; + + port { + axi_hdmi_out: endpoint { + remote-endpoint = <&adv7511_in>; + }; + }; + }; + +// adi axi_spi_engine device tree entry. for additional information see /linux/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.txt // + + + axi_spi_engine_0: axi-spi-engine@10040000 { + compatible = "adi,axi-spi-engine-1.00.a"; + reg = <0x00000001 0x00040000 0x00010000>; + interrupt-parent = <&intc>; + interrupts = <0 46 4>; + clocks = <&spi_engine_clk>, <&spi_clk>; + clock-names = "s_axi_aclk", "spi_clk"; + + #address-cells = <0x1>; + #size-cells = <0x0>; + num-cs = <1>; + + + ad7606b: adc@0 { + compatible = "adi,ad7606b"; + reg = <0>; + spi-max-frequency =<60000000>; + spi-cpol; + avcc-supply = <&vref>; + adi,conversion-start-gpios = <&gpio_altr 9 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio_altr 3 GPIO_ACTIVE_HIGH>; + adi,first-data-gpios = <&gpio_altr 4 GPIO_ACTIVE_HIGH>; + adi,range = <&gpio_altr 5 GPIO_ACTIVE_HIGH>; + adi,oversampling-ratio-gpios = <&gpio_altr 0 GPIO_ACTIVE_HIGH>, + <&gpio_altr 1 GPIO_ACTIVE_HIGH>, + <&gpio_altr 2 GPIO_ACTIVE_HIGH>; + standby-gpios = <&gpio_altr 8 GPIO_ACTIVE_LOW>; + refsel-gpios = <&gpio_altr 6 GPIO_ACTIVE_HIGH>; + adi,sw-mode; + #io-channel-cells = <1>; + #gpio-cells = <2>; + + + }; + }; + + ad7606_dma: ad7606-dma@10070000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x00000001 0x00070000 0x00000800>; + interrupt-parent = <&intc>; + interrupts = <0 47 4>; + #dma-cells = <1>; + clocks = <&dma_clk>; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma-channel@0 { + reg = <0>; + adi,source-bus-width = <128>; + adi,source-bus-type = <2>; + adi,destination-bus-width = <64>; + adi,destination-bus-type = <0>; + }; + }; + }; + + axi_ad7606_adc: axi-ad7606-adc@10060000 { + compatible = "adi,axi-adc-10.0.a"; + reg = <0x00000001 0x00060000 0x00010000>; + dmas = <&ad7606_dma 0>; + dma-names = "rx"; + spibus-connected = <&ad7606b>; + }; + + /* add addiional FPGA peripheral device tree entries here */ + + }; + }; +}; diff --git a/arch/arm/configs/socfpga_tei0022_adi_defconfig b/arch/arm/configs/socfpga_tei0022_adi_defconfig index b1a7b7d8978e..fe2dcbba59b1 100644 --- a/arch/arm/configs/socfpga_tei0022_adi_defconfig +++ b/arch/arm/configs/socfpga_tei0022_adi_defconfig @@ -210,3 +210,6 @@ CONFIG_IIO_BUFFER CONFIG_IIO_BUFFER_HW_CONSUMER CONFIG_IIO_BUFFER_DMAENGINE +CONFIG_AD7606=y +CONFIG_AD7606_IFACE_SPI=y +#CONFIG_AD7606_IFACE_PARALLEL=y diff --git a/drivers/iio/adc/ad7606.c b/drivers/iio/adc/ad7606.c index cf5b8de1b4ac..8b37dec8b136 100644 --- a/drivers/iio/adc/ad7606.c +++ b/drivers/iio/adc/ad7606.c @@ -17,6 +17,8 @@ #include #include +#include + #include #include #include @@ -24,9 +26,20 @@ #include #include -#include +#include +#include +#include +#include +#include #include "ad7606.h" +#include "cf_axi_adc.h" + + + +#define AD7606_OUTPUT_MODE_TWOS_COMPLEMENT 0x01 + +uint8_t exec = 0; /* * Scales are computed as 5000/32768 and 10000/32768 respectively, @@ -48,6 +61,20 @@ static const unsigned int ad7616_oversampling_avail[8] = { 1, 2, 4, 8, 16, 32, 64, 128, }; +static const struct axiadc_chip_info conv_chip_info = { + .name = "ad7606_axi_adc", + .max_rate = 800000UL, + .num_channels = 8, + .channel[0] = AD7606B_CHANNEL(0), + .channel[1] = AD7606B_CHANNEL(1), + .channel[2] = AD7606B_CHANNEL(2), + .channel[3] = AD7606B_CHANNEL(3), + .channel[4] = AD7606B_CHANNEL(4), + .channel[5] = AD7606B_CHANNEL(5), + .channel[6] = AD7606B_CHANNEL(6), + .channel[7] = AD7606B_CHANNEL(7), +}; + static int ad7606_reset(struct ad7606_state *st) { if (st->gpio_reset) { @@ -60,13 +87,53 @@ static int ad7606_reset(struct ad7606_state *st) return -ENODEV; } +static bool ad7606_has_axi_adc(struct device *dev) +{ + return device_property_present(dev, "spibus-connected"); +} + +static struct ad7606_state *ad7606_get_data(struct iio_dev *indio_dev) +{ + struct axiadc_converter *conv; + + if (ad7606_has_axi_adc(&indio_dev->dev)) { + /* AXI ADC*/ + conv = iio_device_get_drvdata(indio_dev); + return conv->phy; + } else { + return iio_priv(indio_dev); + } +} + +static int ad7606_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = ad7606_get_data(indio_dev); + int ret=0; + exec = 1; + ret =st->bops->offload_enable(st); + return 0; +} + +static int ad7606_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = ad7606_get_data(indio_dev); + int ret=0; + ret =st->bops->offload_disable(st); + exec =0; + return 0; +} + static int ad7606_reg_access(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { - struct ad7606_state *st = iio_priv(indio_dev); - int ret; + struct ad7606_state *st = ad7606_get_data(indio_dev); + int ret=0; + + if (exec){ + ad7606_buffer_predisable(indio_dev); + } mutex_lock(&st->lock); if (readval) { @@ -78,48 +145,24 @@ static int ad7606_reg_access(struct iio_dev *indio_dev, } else { ret = st->bops->reg_write(st, reg, writeval); } + err_unlock: mutex_unlock(&st->lock); return ret; } + static int ad7606_read_samples(struct ad7606_state *st) { unsigned int num = st->chip_info->num_channels - 1; - u16 *data = st->data; - int ret; - - /* - * The frstdata signal is set to high while and after reading the sample - * of the first channel and low for all other channels. This can be used - * to check that the incoming data is correctly aligned. During normal - * operation the data should never become unaligned, but some glitch or - * electrostatic discharge might cause an extra read or clock cycle. - * Monitoring the frstdata signal allows to recover from such failure - * situations. - */ - - if (st->gpio_frstdata) { - ret = st->bops->read_block(st->dev, 1, data); - if (ret) - return ret; - - if (!gpiod_get_value(st->gpio_frstdata)) { - ad7606_reset(st); - return -EIO; - } - - data++; - num--; - } - - return st->bops->read_block(st->dev, num, data); + u16 *data; + return st->bops->read_block(st, num, data); } static irqreturn_t ad7606_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; - struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_state *st = ad7606_get_data(indio_dev); int ret; mutex_lock(&st->lock); @@ -140,24 +183,25 @@ static irqreturn_t ad7606_trigger_handler(int irq, void *p) static int ad7606_scan_direct(struct iio_dev *indio_dev, unsigned int ch) { - struct ad7606_state *st = iio_priv(indio_dev); - int ret; + struct ad7606_state *st = ad7606_get_data(indio_dev); + int ret;u16 *data = st->data; + if (exec){ + ad7606_buffer_predisable(indio_dev); + } - gpiod_set_value(st->gpio_convst, 1); ret = wait_for_completion_timeout(&st->completion, msecs_to_jiffies(1000)); if (!ret) { ret = -ETIMEDOUT; - goto error_ret; + //goto error_ret; } - + int i; ret = ad7606_read_samples(st); if (ret == 0) - ret = st->data[ch]; + ret = st->data[2+ch]; error_ret: - gpiod_set_value(st->gpio_convst, 0); - + return ret; return ret; } @@ -168,8 +212,8 @@ static int ad7606_read_raw(struct iio_dev *indio_dev, long m) { int ret, ch = 0; - struct ad7606_state *st = iio_priv(indio_dev); - + struct ad7606_state *st = ad7606_get_data(indio_dev); + switch (m) { case IIO_CHAN_INFO_RAW: ret = iio_device_claim_direct_mode(indio_dev); @@ -189,6 +233,9 @@ static int ad7606_read_raw(struct iio_dev *indio_dev, *val = 0; *val2 = st->scale_avail[st->range[ch]]; return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = 800000; + return IIO_VAL_INT; case IIO_CHAN_INFO_OVERSAMPLING_RATIO: *val = st->oversampling; return IIO_VAL_INT; @@ -226,7 +273,6 @@ static IIO_DEVICE_ATTR_RO(in_voltage_scale_available, 0); static int ad7606_write_scale_hw(struct iio_dev *indio_dev, int ch, int val) { struct ad7606_state *st = iio_priv(indio_dev); - gpiod_set_value(st->gpio_range, val); return 0; @@ -257,7 +303,7 @@ static int ad7606_write_raw(struct iio_dev *indio_dev, int val2, long mask) { - struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_state *st = ad7606_get_data(indio_dev); int i, ret, ch = 0; switch (mask) { @@ -358,6 +404,18 @@ static const struct iio_chan_spec ad7606_channels[] = { AD7606_CHANNEL(7), }; +static const struct iio_chan_spec ad7606B_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(8), + AD7606B_CHANNEL(0), + AD7606B_CHANNEL(1), + AD7606B_CHANNEL(2), + AD7606B_CHANNEL(3), + AD7606B_CHANNEL(4), + AD7606B_CHANNEL(5), + AD7606B_CHANNEL(6), + AD7606B_CHANNEL(7), +}; + /* * The current assumption that this driver makes for AD7616, is that it's * working in Hardware Mode with Serial, Burst and Sequencer modes activated. @@ -388,6 +446,9 @@ static const struct iio_chan_spec ad7616_channels[] = { AD7606_CHANNEL(15), }; + + + static const struct ad7606_chip_info ad7606_chip_info_tbl[] = { /* More devices added in future */ [ID_AD7605_4] = { @@ -432,11 +493,10 @@ static int ad7606_request_gpios(struct ad7606_state *st) { struct device *dev = st->dev; - st->gpio_convst = devm_gpiod_get(dev, "adi,conversion-start", - GPIOD_OUT_LOW); + st->gpio_convst = devm_gpiod_get(dev, "adi,conversion-start",GPIOD_OUT_LOW); if (IS_ERR(st->gpio_convst)) return PTR_ERR(st->gpio_convst); - + st->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(st->gpio_reset)) return PTR_ERR(st->gpio_reset); @@ -474,8 +534,7 @@ static int ad7606_request_gpios(struct ad7606_state *st) static irqreturn_t ad7606_interrupt(int irq, void *dev_id) { struct iio_dev *indio_dev = dev_id; - struct ad7606_state *st = iio_priv(indio_dev); - + struct ad7606_state *st = ad7606_get_data(indio_dev); if (iio_buffer_enabled(indio_dev)) { gpiod_set_value(st->gpio_convst, 0); iio_trigger_poll_chained(st->trig); @@ -489,31 +548,25 @@ static irqreturn_t ad7606_interrupt(int irq, void *dev_id) static int ad7606_validate_trigger(struct iio_dev *indio_dev, struct iio_trigger *trig) { - struct ad7606_state *st = iio_priv(indio_dev); - + struct ad7606_state *st = ad7606_get_data(indio_dev); if (st->trig != trig) return -EINVAL; return 0; } -static int ad7606_buffer_postenable(struct iio_dev *indio_dev) +static int hw_submit_block(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) { - struct ad7606_state *st = iio_priv(indio_dev); - - gpiod_set_value(st->gpio_convst, 1); - - return 0; + block->block.bytes_used = block->block.size; + return iio_dmaengine_buffer_submit_block(queue, block, DMA_DEV_TO_MEM); } -static int ad7606_buffer_predisable(struct iio_dev *indio_dev) -{ - struct ad7606_state *st = iio_priv(indio_dev); - gpiod_set_value(st->gpio_convst, 0); - - return 0; -} +static const struct iio_dma_buffer_ops dma_buffer_ops = { + .submit = hw_submit_block, + .abort = iio_dmaengine_buffer_abort, +}; static const struct iio_buffer_setup_ops ad7606_buffer_ops = { .postenable = &ad7606_buffer_postenable, @@ -548,6 +601,7 @@ static const struct iio_info ad7606_info_os = { }; static const struct iio_info ad7606_info_range = { + .attrs = &ad7606_attribute_group_os_and_range, .read_raw = &ad7606_read_raw, .write_raw = &ad7606_write_raw, .attrs = &ad7606_attribute_group_range, @@ -558,11 +612,56 @@ static const struct iio_trigger_ops ad7606_trigger_ops = { .validate_device = iio_trigger_validate_own_device, }; + static void ad7606_regulator_disable(void *data) { struct ad7606_state *st = data; - regulator_disable(st->reg); +} + +static int ad7606_post_setup(struct iio_dev *indio_dev){ + indio_dev->setup_ops = &ad7606_buffer_ops; + return 0; +} + +static int ad7606_register_axi_adc(struct ad7606_state *st, struct iio_dev *indio_dev) +{ + struct axiadc_converter *conv; + struct spi_device *spi = to_spi_device(st->dev); + + conv = devm_kzalloc(st->dev, sizeof(*conv), GFP_KERNEL); + if (conv == NULL) + return -ENOMEM; + + conv->spi = spi; + conv->clk = NULL; + conv->chip_info = &conv_chip_info; + conv->adc_output_mode = AD7606_OUTPUT_MODE_TWOS_COMPLEMENT; + conv->reg_access = &ad7606_reg_access; + conv->write_raw = &ad7606_write_raw; + conv->read_raw = &ad7606_read_raw; + conv->post_setup = &ad7606_post_setup; + conv->attrs = &ad7606_attribute_group_os_and_range; + conv->phy = st; + /* Without this, the axi_adc won't find the converter data */ + spi_set_drvdata(spi, conv); + + return 0; +} + + + +static int ad7606_register(struct ad7606_state *st, struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer; + buffer = devm_iio_dmaengine_buffer_alloc(indio_dev->dev.parent, "rx", + &dma_buffer_ops, indio_dev); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); + + iio_device_attach_buffer(indio_dev, buffer); + + return devm_iio_device_register(st->dev, indio_dev); } int ad7606_probe(struct device *dev, int irq, void __iomem *base_address, @@ -572,14 +671,13 @@ int ad7606_probe(struct device *dev, int irq, void __iomem *base_address, struct ad7606_state *st; int ret; struct iio_dev *indio_dev; - indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); if (!indio_dev) return -ENOMEM; st = iio_priv(indio_dev); dev_set_drvdata(dev, indio_dev); - + st->dev = dev; mutex_init(&st->lock); st->bops = bops; @@ -588,7 +686,7 @@ int ad7606_probe(struct device *dev, int irq, void __iomem *base_address, st->range[0] = 0; st->oversampling = 1; st->scale_avail = ad7606_scale_avail; - st->num_scales = ARRAY_SIZE(ad7606_scale_avail); + st->num_scales = ARRAY_SIZE(ad7606B_scale_avail); st->reg = devm_regulator_get(dev, "avcc"); if (IS_ERR(st->reg)) @@ -627,12 +725,13 @@ int ad7606_probe(struct device *dev, int irq, void __iomem *base_address, else indio_dev->info = &ad7606_info_no_os_or_range; } - - indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_HARDWARE; indio_dev->name = name; indio_dev->channels = st->chip_info->channels; indio_dev->num_channels = st->chip_info->num_channels; - + indio_dev->setup_ops = &ad7606_buffer_ops; + ret = ad7606_reset(st); if (ret) dev_warn(st->dev, "failed to RESET: no RESET GPIO specified\n"); @@ -652,49 +751,24 @@ int ad7606_probe(struct device *dev, int irq, void __iomem *base_address, if (st->sw_mode_en) { indio_dev->info = &ad7606_info_os_range_and_debug; - /* Scale of 0.076293 is only available in sw mode */ st->scale_avail = ad7606B_scale_avail; st->num_scales = ARRAY_SIZE(ad7606B_scale_avail); - /* After reset, in software mode, ±10 V is set by default */ - memset32(st->range, 2, ARRAY_SIZE(st->range)); - + memset32(st->range, 1, ARRAY_SIZE(st->range)); ret = st->bops->sw_mode_config(indio_dev); } init_completion(&st->completion); - st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", - indio_dev->name, indio_dev->id); - if (!st->trig) - return -ENOMEM; - - st->trig->ops = &ad7606_trigger_ops; - st->trig->dev.parent = dev; - iio_trigger_set_drvdata(st->trig, indio_dev); - ret = devm_iio_trigger_register(dev, st->trig); - if (ret) - return ret; - - indio_dev->trig = iio_trigger_get(st->trig); - - ret = devm_request_threaded_irq(dev, irq, - NULL, - &ad7606_interrupt, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - name, indio_dev); - if (ret) - return ret; - - ret = devm_iio_triggered_buffer_setup(dev, indio_dev, - &iio_pollfunc_store_time, - &ad7606_trigger_handler, - &ad7606_buffer_ops); - if (ret) - return ret; - - return devm_iio_device_register(dev, indio_dev); + /* If there is a reference to a dma channel, the device is not using + * the axi adc + */ + if (device_property_present(st->dev, "dmas")) + ret = ad7606_register(st, indio_dev); + else + ret = ad7606_register_axi_adc(st,indio_dev); + return ret; } EXPORT_SYMBOL_GPL(ad7606_probe); diff --git a/drivers/iio/adc/ad7606.h b/drivers/iio/adc/ad7606.h index bf0072cb812a..90a1a97e042f 100644 --- a/drivers/iio/adc/ad7606.h +++ b/drivers/iio/adc/ad7606.h @@ -39,7 +39,10 @@ #define AD7606B_CHANNEL(num) \ AD760X_CHANNEL(num, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),\ - 0, BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO)) + 0,BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | BIT(IIO_CHAN_INFO_SAMP_FREQ)) + + + /** * struct ad7606_chip_info - chip specific information @@ -116,6 +119,10 @@ struct ad7606_state { struct gpio_descs *gpio_os; struct iio_trigger *trig; struct completion completion; + int irq; +//added by shivashankar + + /* * DMA (thus cache coherency maintenance) requires the @@ -125,6 +132,9 @@ struct ad7606_state { unsigned short data[20] ____cacheline_aligned; }; + + + /** * struct ad7606_bus_ops - driver bus operations * @read_block function pointer for reading blocks of data @@ -137,8 +147,10 @@ struct ad7606_state { */ struct ad7606_bus_ops { /* more methods added in future? */ - int (*read_block)(struct device *dev, int num, void *data); + int (*read_block)(struct ad7606_state *st, int num, void *data); int (*reg_read)(struct ad7606_state *st, unsigned int addr); + int (*offload_enable)(struct ad7606_state *st); + int (*offload_disable)(struct ad7606_state *st); int (*reg_write)(struct ad7606_state *st, unsigned int addr, unsigned int val); diff --git a/drivers/iio/adc/ad7606_par.c b/drivers/iio/adc/ad7606_par.c index a1d5f76fa9f7..6bf79f70d3de 100644 --- a/drivers/iio/adc/ad7606_par.c +++ b/drivers/iio/adc/ad7606_par.c @@ -4,7 +4,7 @@ * * Copyright 2011 Analog Devices Inc. */ - +#include #include #include #include @@ -12,93 +12,299 @@ #include #include +#include #include "ad7606.h" -static int ad7606_par16_read_block(struct device *dev, +#define burst_length_reg ((0x112)<<2) +#define data_write_reg ((0x114)<<2) +#define data_read_reg ((0x113)<<2) +#define cnvst_en_reg ((0x110)<<2) +#define up_cnv_rate_reg ((0x111)<<2) +#define PCORE_REG_VERSION ((0x100)<<2) +#define PCORE_VERSION(x) ((x >> 12) & 0xf) + +#define AD7606_CONFIGURATION_REGISTER 0x02 +#define AD7606_SINGLE_DOUT 0x0 +#define AD7606_QUAD_DOUT 0x10 + +/* AD7606_RANGE_CH_X_Y */ +#define AD7606_RANGE_CH_MSK(ch) (GENMASK(3, 0) << (4 * ((ch) % 2))) +#define AD7606_RANGE_CH_MODE(ch, mode) \ + ((GENMASK(3, 0) & mode) << (4 * ((ch) % 2))) +#define AD7606_RANGE_CH_ADDR(ch) (0x03 + ((ch) >> 1)) +#define AD7606_OS_MODE 0x08 + +struct spi_engine_program { + unsigned int length; + uint16_t instructions[]; +}; + +struct spi_engine { + struct clk *clk; + struct clk *ref_clk; + + struct spi_master *master; + + spinlock_t lock; + + void __iomem *base; + + struct spi_message *msg; + struct spi_engine_program *p; + unsigned cmd_length; + const uint16_t *cmd_buf; + + struct spi_transfer *tx_xfer; + unsigned int tx_length; + const uint8_t *tx_buf; + + struct spi_transfer *rx_xfer; + unsigned int rx_length; + uint8_t *rx_buf; + + unsigned int sync_id; + unsigned int completed_id; + + unsigned int int_enable; + + struct timer_list watchdog_timer; + unsigned int word_length; +}; + +static const unsigned int ad7606B_oversampling_avail[9] = { + 1, 2, 4, 8, 16, 32, 64, 128, 256 +}; + +static const struct iio_chan_spec ad7606B_soft_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(8), + AD7606B_CHANNEL(0), + AD7606B_CHANNEL(1), + AD7606B_CHANNEL(2), + AD7606B_CHANNEL(3), + AD7606B_CHANNEL(4), + AD7606B_CHANNEL(5), + AD7606B_CHANNEL(6), + AD7606B_CHANNEL(7), +}; + +static u16 ad7606B_par_rd_wr_cmd(int addr, char isWriteOp) +{ + return (addr & 0x3F) | (((~isWriteOp) & 0x1) << 6); +} + +static void ad7606_par_burst_length(struct ad7606_state *st, + int count) +{ + writel(count , (unsigned long)st->base_address + burst_length_reg); +} + +static int ad7606_par16_read_block(struct ad7606_state *st, int count, void *buf) { - struct iio_dev *indio_dev = dev_get_drvdata(dev); - struct ad7606_state *st = iio_priv(indio_dev); + int i; + unsigned short *data = buf; + ad7606_par_burst_length(st,0); + //writel(0xd0,(unsigned long)st->base_address + up_cnv_rate_reg);// to configure convst rate + //writel(0x03,(unsigned long)st->base_address + cnvst_en_reg);// to enable convst signal + //ndelay(800); + //writel(0x00,(unsigned long)st->base_address + cnvst_en_reg);// to disable convst signal + for(i=0; idata[i+2]=(u16)(readl((unsigned long)st->base_address + data_read_reg));} + return 0; +} - insw((unsigned long)st->base_address, buf, count); +static int ad7606_par_reg_write(struct ad7606_state *st, + unsigned int addr, unsigned int val) +{ + uint16_t temp; + ad7606_par_burst_length(st,0); + temp= (u16)(((addr & 0x007f)<<8) + (val & 0x00ff)); + writel( temp , (unsigned long)st->base_address + data_write_reg); return 0; } -static const struct ad7606_bus_ops ad7606_par16_bops = { - .read_block = ad7606_par16_read_block, -}; +static int ad7606_par_reg_read(struct ad7606_state *st, + unsigned int addr) +{ + uint16_t temp;uint16_t temp1; + ad7606_par_burst_length(st,0); + temp= (u16)((0x80 | (addr & 0x007f))<<8); + writel( temp , (unsigned long)st->base_address + data_write_reg); + temp1= (uint16_t)(readl((unsigned long)st->base_address + data_read_reg)); + return temp1; +} -static int ad7606_par8_read_block(struct device *dev, - int count, void *buf) +static int ad7606_par_data_read(struct ad7606_state *st) { - struct iio_dev *indio_dev = dev_get_drvdata(dev); - struct ad7606_state *st = iio_priv(indio_dev); + unsigned int reg; + writel(0xd0,(unsigned long)st->base_address + up_cnv_rate_reg);// to configure convst rate + writel(0x03,(unsigned long)st->base_address + cnvst_en_reg);// to enable convst signal + ad7606_par_burst_length(st,3); + reg=readl((unsigned long)st->base_address + data_read_reg); + + return 0; +} - insb((unsigned long)st->base_address, buf, count * 2); +static int ad7606_par_data_write(struct ad7606_state *st) +{ + unsigned int reg; + writel(0x00,(unsigned long)st->base_address + cnvst_en_reg);// to disable convst signal + ad7606_par_burst_length(st,1); return 0; } -static const struct ad7606_bus_ops ad7606_par8_bops = { - .read_block = ad7606_par8_read_block, -}; +static int ad7606_par_write_mask(struct ad7606_state *st, + unsigned int addr, + unsigned long mask, + unsigned int val) +{ + int readval=0; + + readval = ad7606_par_reg_read(st, addr); + if (readval < 0) + return readval; + + readval &= ~mask; + readval |= val; -static int ad7606_par_probe(struct platform_device *pdev) + return ad7606_par_reg_write(st, addr, readval); +} + +static int ad7606_write_scale_sw(struct iio_dev *indio_dev, int ch, int val) { - const struct platform_device_id *id = platform_get_device_id(pdev); - struct resource *res; - void __iomem *addr; - resource_size_t remap_size; - int irq; + struct ad7606_state *st = iio_priv(indio_dev); + return ad7606_par_write_mask(st, + AD7606_RANGE_CH_ADDR(ch), + AD7606_RANGE_CH_MSK(ch), + AD7606_RANGE_CH_MODE(ch, val)); +} + +static int ad7606_write_os_sw(struct iio_dev *indio_dev, int val) +{ + struct ad7606_state *st = iio_priv(indio_dev); + return ad7606_par_reg_write(st, AD7606_OS_MODE, val); +} - irq = platform_get_irq(pdev, 0); - if (irq < 0) { - dev_err(&pdev->dev, "no irq: %d\n", irq); - return irq; + +static int ad7606B_sw_mode_config(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + unsigned int buf[3]; + + /* + * Software mode is enabled when all three oversampling + * pins are set to high. If oversampling gpios are defined + * in the device tree, then they need to be set to high, + * otherwise, they must be hardwired to VDD + */ + if (st->gpio_os) { + memset32(buf, 1, ARRAY_SIZE(buf)); + gpiod_set_array_value(ARRAY_SIZE(buf), + st->gpio_os->desc, buf); } + /* OS of 128 and 256 are available only in softwar(spi, false);*/ + st->oversampling_avail = ad7606B_oversampling_avail; + st->num_os_ratios = ARRAY_SIZE(ad7606B_oversampling_avail); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - addr = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(addr)) - return PTR_ERR(addr); + st->write_scale = ad7606_write_scale_sw; + st->write_os = &ad7606_write_os_sw; - remap_size = resource_size(res); + /* Configure device spi to output on a single channel */ + st->bops->reg_write(st, + AD7606_CONFIGURATION_REGISTER, + AD7606_QUAD_DOUT); //converted to the quad Dout by user + /* + * Scale can be configured individually for each channel + * in software mode. + */ + indio_dev->channels = ad7606B_soft_channels; - return ad7606_probe(&pdev->dev, irq, addr, - id->name, id->driver_data, - remap_size > 1 ? &ad7606_par16_bops : - &ad7606_par8_bops); + return 0; } -static const struct platform_device_id ad7606_driver_ids[] = { - { .name = "ad7605-4", .driver_data = ID_AD7605_4, }, - { .name = "ad7606-4", .driver_data = ID_AD7606_4, }, - { .name = "ad7606-6", .driver_data = ID_AD7606_6, }, - { .name = "ad7606-8", .driver_data = ID_AD7606_8, }, - { } +static const struct ad7606_bus_ops ad7606_par16_bops = { + .read_block = ad7606_par16_read_block, }; -MODULE_DEVICE_TABLE(platform, ad7606_driver_ids); + +static const struct ad7606_bus_ops ad7606B_par_bops = { + .read_block = ad7606_par16_read_block, + .offload_enable = ad7606_par_data_read, + .offload_disable = ad7606_par_data_write, + .reg_read = ad7606_par_reg_read, + .reg_write = ad7606_par_reg_write, + .write_mask = ad7606_par_write_mask, + .rd_wr_cmd = ad7606B_par_rd_wr_cmd, + .sw_mode_config = ad7606B_sw_mode_config, +}; + + +static int spi_engine_transfer_one_message(struct spi_master *master, + struct spi_message *msg) +{ + printk("dummy function for probing"); +} + +static int ad7606_par_probe(struct spi_device *spi) +{ + void __iomem *addr; + const struct spi_device_id *id = spi_get_device_id(spi); + const struct ad7606_bus_ops *bops; + + switch (id->driver_data) { + case ID_AD7616: + bops = &ad7606B_par_bops; + break; + case ID_AD7606B: + bops = &ad7606B_par_bops; + break; + default: + bops = &ad7606B_par_bops; + break; + } + + struct spi_engine *spi_engine = spi_master_get_devdata(spi->master); + addr = spi_engine->base; + if (IS_ERR(addr)) + return PTR_ERR(addr); + + return ad7606_probe(&spi->dev, 1,addr, + id->name,id->driver_data, + bops); +} static const struct of_device_id ad7606_of_match[] = { { .compatible = "adi,ad7605-4" }, { .compatible = "adi,ad7606-4" }, { .compatible = "adi,ad7606-6" }, { .compatible = "adi,ad7606-8" }, + { .compatible = "adi,ad7606b" }, { }, }; MODULE_DEVICE_TABLE(of, ad7606_of_match); -static struct platform_driver ad7606_driver = { - .probe = ad7606_par_probe, - .id_table = ad7606_driver_ids, +static const struct spi_device_id ad7606_driver_ids[] = { + { "ad7605-4", ID_AD7605_4 }, + { "ad7606-4", ID_AD7606_4 }, + { "ad7606-6", ID_AD7606_6 }, + { "ad7606-8", ID_AD7606_8 }, + { "ad7606b", ID_AD7606B }, + { "ad7616", ID_AD7616 }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7606_driver_ids); + +static struct spi_driver ad7606_driver = { .driver = { .name = "ad7606", - .pm = AD7606_PM_OPS, .of_match_table = ad7606_of_match, + .pm = AD7606_PM_OPS, }, + .probe = ad7606_par_probe, + .id_table = ad7606_driver_ids, }; -module_platform_driver(ad7606_driver); +module_spi_driver(ad7606_driver); MODULE_AUTHOR("Michael Hennerich "); MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); diff --git a/drivers/iio/adc/ad7606_spi.c b/drivers/iio/adc/ad7606_spi.c index cd407b8c3d00..c0ee33df2972 100644 --- a/drivers/iio/adc/ad7606_spi.c +++ b/drivers/iio/adc/ad7606_spi.c @@ -10,11 +10,28 @@ #include #include +#include #include + #include "ad7606.h" #define MAX_SPI_FREQ_HZ 23500000 /* VDRIVE above 4.75 V */ +#define burst_length_reg ((0x112)<<2) +#define data_write_reg ((0x114)<<2) +#define data_read_reg ((0x113)<<2) +#define cnvst_en_reg ((0x110)<<2) +#define up_cnv_rate_reg ((0x111)<<2) +#define PCORE_REG_VERSION ((0x100)<<2) +#define PCORE_VERSION(x) ((x >> 12) & 0xf) + +/* AD7606_RANGE_CH_X_Y */ +#define AD7606_RANGE_CH_MSK(ch) (GENMASK(3, 0) << (4 * ((ch) % 2))) +#define AD7606_RANGE_CH_MODE(ch, mode) \ + ((GENMASK(3, 0) & mode) << (4 * ((ch) % 2))) +#define AD7606_RANGE_CH_ADDR(ch) (0x03 + ((ch) >> 1)) +#define AD7606_OS_MODE 0x08 + #define AD7616_CONFIGURATION_REGISTER 0x02 #define AD7616_OS_MASK GENMASK(4, 2) #define AD7616_BURST_MODE BIT(6) @@ -26,6 +43,7 @@ #define AD7606_CONFIGURATION_REGISTER 0x02 #define AD7606_SINGLE_DOUT 0x0 +#define AD7606_QUAD_DOUT 0x10 /* AD7606_RANGE_CH_X_Y */ #define AD7606_RANGE_CH_MSK(ch) (GENMASK(3, 0) << (4 * ((ch) % 2))) @@ -80,48 +98,70 @@ static u16 ad7616_spi_rd_wr_cmd(int addr, char isWriteOp) return ((addr & 0x7F) << 1) | ((isWriteOp & 0x1) << 7); } -static int ad7606_spi_read_block(struct device *dev, +static int ad7606_spi_read_block(struct ad7606_state *st, int count, void *buf) { - struct spi_device *spi = to_spi_device(dev); - int i, ret; - unsigned short *data = buf; - __be16 *bdata = buf; - - ret = spi_read(spi, buf, count * 2); - if (ret < 0) { - dev_err(&spi->dev, "SPI read error\n"); + int ret; + struct spi_device *spi = to_spi_device(st->dev); + + struct spi_transfer xfer[] = { + { + .rx_buf = &st->data[2], + .len = 4, + .bits_per_word = 32, + }, + { + .rx_buf = &st->data[4], + .len = 4, + .bits_per_word = 32, + }, + { + .rx_buf = &st->data[6], + .len = 4, + .bits_per_word = 32, + }, + { + .rx_buf = &st->data[8], + .len = 4, + .bits_per_word = 32, + } + + }; + + uint8_t val = st->bops->reg_read(st,AD7606_CONFIGURATION_REGISTER); + st->bops->reg_write(st,AD7606_CONFIGURATION_REGISTER,AD7606_SINGLE_DOUT);// in single dout mode + spi_engine_write_reg(spi,0x111,0x82);//to configure up_cnv rate + spi_engine_write_reg(spi,0x110,0x03);//to enable cnvst and reset high + ndelay(800); + spi_engine_write_reg(spi,0x110,0x00);//to disable cnvst and reset low + ret = spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer)); + if (ret < 0) return ret; - } - - for (i = 0; i < count; i++) - data[i] = be16_to_cpu(bdata[i]); + st->bops->reg_write(st,AD7606_CONFIGURATION_REGISTER, val);//put in default setting return 0; } + static int ad7606_spi_reg_read(struct ad7606_state *st, unsigned int addr) { struct spi_device *spi = to_spi_device(st->dev); - struct spi_transfer t[] = { - { + + struct spi_transfer t[] = { + { .tx_buf = &st->data[0], - .len = 2, - .cs_change = 0, - }, { - .rx_buf = &st->data[1], - .len = 2, - }, - }; - int ret; + .rx_buf = &st->data[2], + .len = 4, + .bits_per_word = 32, + } + }; + int ret; st->data[0] = cpu_to_be16(st->bops->rd_wr_cmd(addr, 0) << 8); - ret = spi_sync_transfer(spi, t, ARRAY_SIZE(t)); if (ret < 0) return ret; - - return be16_to_cpu(st->data[1]); + return be16_to_cpu(st->data[2]) >> 8; } static int ad7606_spi_reg_write(struct ad7606_state *st, @@ -129,13 +169,51 @@ static int ad7606_spi_reg_write(struct ad7606_state *st, unsigned int val) { struct spi_device *spi = to_spi_device(st->dev); - st->data[0] = cpu_to_be16((st->bops->rd_wr_cmd(addr, 1) << 8) | (val & 0x1FF)); return spi_write(spi, &st->data[0], sizeof(st->data[0])); } + +static int ad7606_spi_offload_enable(struct ad7606_state *st) +{ + struct spi_device *spi = to_spi_device(st->dev); + struct spi_message msg; + int ret; + unsigned int rx_data[8]; + + struct spi_transfer xfer[] = { + { + .rx_buf = rx_data, + .len = 16, + .bits_per_word = 32, + } + }; + + spi_engine_write_reg(spi,0x111,0x82);//to configure up_cnv rate BE + spi_engine_write_reg(spi,0x110,0x03);//to enable cnvst and reset high + + spi_bus_lock(spi->master); + spi_message_init_with_transfers(&msg, xfer, ARRAY_SIZE(xfer)); + ret = spi_engine_offload_load_msg(spi, &msg); + if (ret < 0) + return ret; + spi_engine_offload_enable(spi, true); + + return ret; +} + +static int ad7606_spi_offload_disable(struct ad7606_state *st) +{ + struct spi_device *spi = to_spi_device(st->dev); + spi_engine_write_reg(spi,0x110,0x00);//to disable cnvst and reset low + spi_engine_offload_enable(spi, false); + spi_bus_unlock(spi->master); + + return 0; +} + static int ad7606_spi_write_mask(struct ad7606_state *st, unsigned int addr, unsigned long mask, @@ -176,7 +254,6 @@ static int ad7616_write_os_sw(struct iio_dev *indio_dev, int val) static int ad7606_write_scale_sw(struct iio_dev *indio_dev, int ch, int val) { struct ad7606_state *st = iio_priv(indio_dev); - return ad7606_spi_write_mask(st, AD7606_RANGE_CH_ADDR(ch), AD7606_RANGE_CH_MSK(ch), @@ -186,7 +263,6 @@ static int ad7606_write_scale_sw(struct iio_dev *indio_dev, int ch, int val) static int ad7606_write_os_sw(struct iio_dev *indio_dev, int val) { struct ad7606_state *st = iio_priv(indio_dev); - return ad7606_spi_reg_write(st, AD7606_OS_MODE, val); } @@ -206,7 +282,7 @@ static int ad7606B_sw_mode_config(struct iio_dev *indio_dev) gpiod_set_array_value(ARRAY_SIZE(buf), st->gpio_os->desc, buf); } - /* OS of 128 and 256 are available only in software mode */ + /* OS of 128 and 256 are available only in softwar(spi, false);*/ st->oversampling_avail = ad7606B_oversampling_avail; st->num_os_ratios = ARRAY_SIZE(ad7606B_oversampling_avail); @@ -216,7 +292,7 @@ static int ad7606B_sw_mode_config(struct iio_dev *indio_dev) /* Configure device spi to output on a single channel */ st->bops->reg_write(st, AD7606_CONFIGURATION_REGISTER, - AD7606_SINGLE_DOUT); + AD7606_QUAD_DOUT); //converted to the quad Dout /* * Scale can be configured individually for each channel * in software mode. @@ -246,6 +322,8 @@ static int ad7616_sw_mode_config(struct iio_dev *indio_dev) AD7616_BURST_MODE | AD7616_SEQEN_MODE); } + + static const struct ad7606_bus_ops ad7606_spi_bops = { .read_block = ad7606_spi_read_block, }; @@ -261,6 +339,8 @@ static const struct ad7606_bus_ops ad7616_spi_bops = { static const struct ad7606_bus_ops ad7606B_spi_bops = { .read_block = ad7606_spi_read_block, + .offload_enable = ad7606_spi_offload_enable, + .offload_disable = ad7606_spi_offload_disable, .reg_read = ad7606_spi_reg_read, .reg_write = ad7606_spi_reg_write, .write_mask = ad7606_spi_write_mask, @@ -284,7 +364,7 @@ static int ad7606_spi_probe(struct spi_device *spi) bops = &ad7606_spi_bops; break; } - + return ad7606_probe(&spi->dev, spi->irq, NULL, id->name, id->driver_data, bops); diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c index 4473ec7f4a19..ccaca819580f 100644 --- a/drivers/spi/spi-axi-spi-engine.c +++ b/drivers/spi/spi-axi-spi-engine.c @@ -1,785 +1,795 @@ -/* - * SPI-Engine SPI controller driver - * Copyright 2015 Analog Devices Inc. - * Author: Lars-Peter Clausen - * - * Licensed under the GPL-2. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SPI_ENGINE_VERSION_MAJOR(x) ((x >> 16) & 0xff) -#define SPI_ENGINE_VERSION_MINOR(x) ((x >> 8) & 0xff) -#define SPI_ENGINE_VERSION_PATCH(x) (x & 0xff) - -#define SPI_ENGINE_REG_VERSION 0x00 - -#define SPI_ENGINE_REG_RESET 0x40 - -#define SPI_ENGINE_REG_INT_ENABLE 0x80 -#define SPI_ENGINE_REG_INT_PENDING 0x84 -#define SPI_ENGINE_REG_INT_SOURCE 0x88 - -#define SPI_ENGINE_REG_SYNC_ID 0xc0 - -#define SPI_ENGINE_REG_CMD_FIFO_ROOM 0xd0 -#define SPI_ENGINE_REG_SDO_FIFO_ROOM 0xd4 -#define SPI_ENGINE_REG_SDI_FIFO_LEVEL 0xd8 - -#define SPI_ENGINE_REG_CMD_FIFO 0xe0 -#define SPI_ENGINE_REG_SDO_DATA_FIFO 0xe4 -#define SPI_ENGINE_REG_SDI_DATA_FIFO 0xe8 -#define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK 0xec - -#define SPI_ENGINE_REG_OFFLOAD_CTRL(x) (0x100 + (0x20 * x)) -#define SPI_ENGINE_REG_OFFLOAD_STATUS(x) (0x104 + (0x20 * x)) -#define SPI_ENGINE_REG_OFFLOAD_RESET(x) (0x108 + (0x20 * x)) -#define SPI_ENGINE_REG_OFFLOAD_CMD_MEM(x) (0x110 + (0x20 * x)) -#define SPI_ENGINE_REG_OFFLOAD_SDO_MEM(x) (0x114 + (0x20 * x)) - -#define SPI_ENGINE_INT_CMD_ALMOST_EMPTY BIT(0) -#define SPI_ENGINE_INT_SDO_ALMOST_EMPTY BIT(1) -#define SPI_ENGINE_INT_SDI_ALMOST_FULL BIT(2) -#define SPI_ENGINE_INT_SYNC BIT(3) - -#define SPI_ENGINE_OFFLOAD_CTRL_ENABLE BIT(0) -#define SPI_ENGINE_OFFLOAD_STATUS_ENABLED BIT(0) - -#define SPI_ENGINE_CONFIG_CPHA BIT(0) -#define SPI_ENGINE_CONFIG_CPOL BIT(1) -#define SPI_ENGINE_CONFIG_3WIRE BIT(2) - -#define SPI_ENGINE_INST_TRANSFER 0x0 -#define SPI_ENGINE_INST_ASSERT 0x1 -#define SPI_ENGINE_INST_WRITE 0x2 -#define SPI_ENGINE_INST_MISC 0x3 - -#define SPI_ENGINE_CMD_REG_CLK_DIV 0x0 -#define SPI_ENGINE_CMD_REG_CONFIG 0x1 -#define SPI_ENGINE_CMD_REG_WORD_LENGTH 0x2 - -#define SPI_ENGINE_MISC_SYNC 0x0 -#define SPI_ENGINE_MISC_SLEEP 0x1 - -#define SPI_ENGINE_TRANSFER_WRITE 0x1 -#define SPI_ENGINE_TRANSFER_READ 0x2 - -#define SPI_ENGINE_CMD(inst, arg1, arg2) \ - (((inst) << 12) | ((arg1) << 8) | (arg2)) - -#define SPI_ENGINE_CMD_TRANSFER(flags, n) \ - SPI_ENGINE_CMD(SPI_ENGINE_INST_TRANSFER, (flags), (n)) -#define SPI_ENGINE_CMD_ASSERT(delay, cs) \ - SPI_ENGINE_CMD(SPI_ENGINE_INST_ASSERT, (delay), (cs)) -#define SPI_ENGINE_CMD_WRITE(reg, val) \ - SPI_ENGINE_CMD(SPI_ENGINE_INST_WRITE, (reg), (val)) -#define SPI_ENGINE_CMD_SLEEP(delay) \ - SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SLEEP, (delay)) -#define SPI_ENGINE_CMD_SYNC(id) \ - SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SYNC, (id)) - -struct spi_engine_program { - unsigned int length; - uint16_t instructions[]; -}; - -struct spi_engine { - struct clk *clk; - struct clk *ref_clk; - - struct spi_master *master; - - spinlock_t lock; - - void __iomem *base; - - struct spi_message *msg; - struct spi_engine_program *p; - unsigned cmd_length; - const uint16_t *cmd_buf; - - struct spi_transfer *tx_xfer; - unsigned int tx_length; - const uint8_t *tx_buf; - - struct spi_transfer *rx_xfer; - unsigned int rx_length; - uint8_t *rx_buf; - - unsigned int sync_id; - unsigned int completed_id; - - unsigned int int_enable; - - struct timer_list watchdog_timer; - unsigned int word_length; -}; - -static void spi_engine_program_add_cmd(struct spi_engine_program *p, - bool dry, uint16_t cmd) -{ - if (!dry) - p->instructions[p->length] = cmd; - p->length++; -} - -static unsigned int spi_engine_get_config(struct spi_device *spi) -{ - unsigned int config = 0; - - if (spi->mode & SPI_CPOL) - config |= SPI_ENGINE_CONFIG_CPOL; - if (spi->mode & SPI_CPHA) - config |= SPI_ENGINE_CONFIG_CPHA; - if (spi->mode & SPI_3WIRE) - config |= SPI_ENGINE_CONFIG_3WIRE; - - return config; -} - -static unsigned int spi_engine_get_clk_div(struct spi_engine *spi_engine, - struct spi_device *spi, struct spi_transfer *xfer) -{ - unsigned int clk_div; - unsigned int speed; - - if (xfer->speed_hz) - speed = xfer->speed_hz; - else - speed = spi->max_speed_hz; - - clk_div = DIV_ROUND_UP(clk_get_rate(spi_engine->ref_clk), - speed * 2); - if (clk_div > 255) - clk_div = 255; - else if (clk_div > 0) - clk_div -= 1; - - return clk_div; -} - -static unsigned int spi_engine_get_word_length(struct spi_engine *spi_engine, - struct spi_device *spi, struct spi_transfer *xfer) -{ - /* if bits_per_word is 0 this will default to 8 bit words */ - if (xfer->bits_per_word) - return xfer->bits_per_word; - else if (spi->bits_per_word) - return spi->bits_per_word; - else - return 8; -} - -static void spi_engine_update_xfer_len(struct spi_engine *spi_engine, - struct spi_transfer *xfer) -{ - unsigned int word_length = spi_engine->word_length; - unsigned int word_len_bytes = word_length / 8; - - if ((xfer->len * 8) < word_length) - xfer->len = 1; - else - xfer->len = DIV_ROUND_UP(xfer->len, word_len_bytes); -} - -static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry, - struct spi_transfer *xfer) -{ - unsigned int len = xfer->len; - - while (len) { - unsigned int n = min(len, 256U); - unsigned int flags = 0; - - if (xfer->tx_buf) - flags |= SPI_ENGINE_TRANSFER_WRITE; - if (xfer->rx_buf) - flags |= SPI_ENGINE_TRANSFER_READ; - - spi_engine_program_add_cmd(p, dry, - SPI_ENGINE_CMD_TRANSFER(flags, n - 1)); - len -= n; - } -} - -static void spi_engine_gen_sleep(struct spi_engine_program *p, bool dry, - struct spi_engine *spi_engine, unsigned int clk_div, unsigned int delay) -{ - unsigned int spi_clk = clk_get_rate(spi_engine->ref_clk); - unsigned int t; - - if (delay == 0) - return; - - t = DIV_ROUND_UP(delay * spi_clk, (clk_div + 1) * 2); - /* spi_clk is in Hz while delay is usec, a division is required */ - t /= 1000000; - while (t) { - unsigned int n = min(t, 256U); - - spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_SLEEP(n - 1)); - t -= n; - } -} - -static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry, - struct spi_device *spi, bool assert) -{ - unsigned int mask = 0xff; - - if (assert) - mask ^= BIT(spi->chip_select); - - spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_ASSERT(1, mask)); -} - -static int spi_engine_compile_message(struct spi_engine *spi_engine, - struct spi_message *msg, bool dry, struct spi_engine_program *p) -{ - struct spi_device *spi = msg->spi; - struct spi_transfer *xfer; - int clk_div, new_clk_div; - int word_len, new_word_len; - bool cs_change = true; - - clk_div = -1; - word_len = -1; - - spi_engine_program_add_cmd(p, dry, - SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CONFIG, - spi_engine_get_config(spi))); - - list_for_each_entry(xfer, &msg->transfers, transfer_list) { - new_clk_div = spi_engine_get_clk_div(spi_engine, spi, xfer); - if (new_clk_div != clk_div) { - clk_div = new_clk_div; - spi_engine_program_add_cmd(p, dry, - SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV, - clk_div)); - } - - new_word_len = spi_engine_get_word_length(spi_engine, spi, xfer); - if (new_word_len != word_len) { - word_len = new_word_len; - spi_engine->word_length = word_len; - spi_engine_program_add_cmd(p, dry, - SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_WORD_LENGTH, - word_len)); - } - - if (cs_change) - spi_engine_gen_cs(p, dry, spi, true); - - spi_engine_update_xfer_len(spi_engine, xfer); - spi_engine_gen_xfer(p, dry, xfer); - spi_engine_gen_sleep(p, dry, spi_engine, clk_div, - xfer->delay_usecs); - - cs_change = xfer->cs_change; - if (list_is_last(&xfer->transfer_list, &msg->transfers)) - cs_change = !cs_change; - - if (cs_change) - spi_engine_gen_cs(p, dry, spi, false); - } - - return 0; -} - -bool spi_engine_offload_supported(struct spi_device *spi) -{ - if (strcmp(spi->master->dev.parent->driver->name, "spi-engine") != 0) - return false; - - return true; -} -EXPORT_SYMBOL_GPL(spi_engine_offload_supported); - -void spi_engine_offload_enable(struct spi_device *spi, bool enable) -{ - struct spi_master *master = spi->master; - struct spi_engine *spi_engine = spi_master_get_devdata(master); - unsigned int reg; - - reg = readl(spi_engine->base + SPI_ENGINE_REG_OFFLOAD_CTRL(0)); - if (enable) - reg |= SPI_ENGINE_OFFLOAD_CTRL_ENABLE; - else - reg &= ~SPI_ENGINE_OFFLOAD_CTRL_ENABLE; - writel(reg, spi_engine->base + SPI_ENGINE_REG_OFFLOAD_CTRL(0)); -} -EXPORT_SYMBOL_GPL(spi_engine_offload_enable); - -int spi_engine_offload_load_msg(struct spi_device *spi, - struct spi_message *msg) -{ - struct spi_master *master = spi->master; - struct spi_engine *spi_engine = spi_master_get_devdata(master); - struct spi_engine_program p_dry; - struct spi_engine_program *p; - struct spi_transfer *xfer; - void __iomem *cmd_addr; - void __iomem *sdo_addr; - const uint8_t *buf; - unsigned int i, j; - size_t size; - - msg->spi = spi; - - p_dry.length = 0; - spi_engine_compile_message(spi_engine, msg, true, &p_dry); - - size = sizeof(*p->instructions) * (p_dry.length + 2); - - p = kzalloc(sizeof(*p) + size, GFP_KERNEL); - if (!p) - return -ENOMEM; - - cmd_addr = spi_engine->base + SPI_ENGINE_REG_OFFLOAD_CMD_MEM(0); - sdo_addr = spi_engine->base + SPI_ENGINE_REG_OFFLOAD_SDO_MEM(0); - - spi_engine_compile_message(spi_engine, msg, false, p); - spi_engine_program_add_cmd(p, false, SPI_ENGINE_CMD_SYNC(0)); - - writel(1, spi_engine->base + SPI_ENGINE_REG_OFFLOAD_RESET(0)); - writel(0, spi_engine->base + SPI_ENGINE_REG_OFFLOAD_RESET(0)); - j = 0; - list_for_each_entry(xfer, &msg->transfers, transfer_list) { - if (!xfer->tx_buf) - continue; - buf = xfer->tx_buf; - for (i = 0; i < xfer->len; i++, j++) - writel(buf[i], sdo_addr); - } - - for (i = 0; i < p->length; i++) - writel(p->instructions[i], cmd_addr); - - kfree(p); - - return 0; -} -EXPORT_SYMBOL_GPL(spi_engine_offload_load_msg); - -static void spi_engine_xfer_next(struct spi_engine *spi_engine, - struct spi_transfer **_xfer) -{ - struct spi_message *msg = spi_engine->msg; - struct spi_transfer *xfer = *_xfer; - - if (!xfer) { - xfer = list_first_entry(&msg->transfers, - struct spi_transfer, transfer_list); - } else if (list_is_last(&xfer->transfer_list, &msg->transfers)) { - xfer = NULL; - } else { - xfer = list_next_entry(xfer, transfer_list); - } - - *_xfer = xfer; -} - -static void spi_engine_tx_next(struct spi_engine *spi_engine) -{ - struct spi_transfer *xfer = spi_engine->tx_xfer; - - do { - spi_engine_xfer_next(spi_engine, &xfer); - } while (xfer && !xfer->tx_buf); - - spi_engine->tx_xfer = xfer; - if (xfer) { - spi_engine->tx_length = xfer->len; - spi_engine->tx_buf = xfer->tx_buf; - } else { - spi_engine->tx_buf = NULL; - } -} - -static void spi_engine_rx_next(struct spi_engine *spi_engine) -{ - struct spi_transfer *xfer = spi_engine->rx_xfer; - - do { - spi_engine_xfer_next(spi_engine, &xfer); - } while (xfer && !xfer->rx_buf); - - spi_engine->rx_xfer = xfer; - if (xfer) { - spi_engine->rx_length = xfer->len; - spi_engine->rx_buf = xfer->rx_buf; - } else { - spi_engine->rx_buf = NULL; - } -} - -static bool spi_engine_write_cmd_fifo(struct spi_engine *spi_engine) -{ - void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_CMD_FIFO; - unsigned int n, m, i; - const uint16_t *buf; - - n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_CMD_FIFO_ROOM); - while (n && spi_engine->cmd_length) { - m = min(n, spi_engine->cmd_length); - buf = spi_engine->cmd_buf; - for (i = 0; i < m; i++) - writel_relaxed(buf[i], addr); - spi_engine->cmd_buf += m; - spi_engine->cmd_length -= m; - n -= m; - } - - return spi_engine->cmd_length != 0; -} - -static void spi_engine_read_buff(struct spi_engine *spi_engine, uint8_t m) -{ - void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDI_DATA_FIFO; - uint32_t val; - uint8_t bytes_len, i, j, *buf; - - bytes_len = DIV_ROUND_UP(spi_engine->word_length, 8); - buf = spi_engine->rx_buf; - - for (i = 0; i < (m * bytes_len); i += bytes_len) { - val = readl_relaxed(addr); - for (j = 0; j < bytes_len; j++) - buf[j] = val >> (8 * j); - buf += bytes_len; - } - - spi_engine->rx_buf += i; -} - -static void spi_engine_write_buff(struct spi_engine *spi_engine, uint8_t m) -{ - void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDO_DATA_FIFO; - uint8_t bytes_len, word_len, i, j; - uint32_t val = 0; - - bytes_len = DIV_ROUND_DOWN_ULL(spi_engine->word_length, 8); - word_len = spi_engine->word_length; - - for (i = 0; i < (m * bytes_len); i += bytes_len) { - for (j = 0; j < bytes_len; j++) - val |= spi_engine->tx_buf[i + j] << (word_len - 8 * (j + 1)); - writel_relaxed(val, addr); - val = 0; - } - - spi_engine->tx_buf += i; -} - -static bool spi_engine_write_tx_fifo(struct spi_engine *spi_engine) -{ - unsigned int n, m; - - n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDO_FIFO_ROOM); - while (n && spi_engine->tx_length) { - m = min(n, spi_engine->tx_length); - spi_engine_write_buff(spi_engine, m); - spi_engine->tx_length -= m; - n -= m; - if (spi_engine->tx_length == 0) - spi_engine_tx_next(spi_engine); - } - - return spi_engine->tx_length != 0; -} - -static bool spi_engine_read_rx_fifo(struct spi_engine *spi_engine) -{ - unsigned int n, m; - - n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDI_FIFO_LEVEL); - while (n && spi_engine->rx_length) { - m = min(n, spi_engine->rx_length); - spi_engine_read_buff(spi_engine, m); - spi_engine->rx_length -= m; - n -= m; - if (spi_engine->rx_length == 0) - spi_engine_rx_next(spi_engine); - } - - return spi_engine->rx_length != 0; -} - -static void spi_engine_complete_message(struct spi_master *master, int status) -{ - struct spi_engine *spi_engine = spi_master_get_devdata(master); - struct spi_message *msg = spi_engine->msg; - - kfree(spi_engine->p); - msg->status = status; - msg->actual_length = msg->frame_length; - spi_engine->msg = NULL; - spi_finalize_current_message(master); -} - -static irqreturn_t spi_engine_irq(int irq, void *devid) -{ - struct spi_master *master = devid; - struct spi_engine *spi_engine = spi_master_get_devdata(master); - unsigned int disable_int = 0; - unsigned int pending; - - pending = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING); - if (pending & SPI_ENGINE_INT_SYNC) { - writel_relaxed(SPI_ENGINE_INT_SYNC, - spi_engine->base + SPI_ENGINE_REG_INT_PENDING); - spi_engine->completed_id = readl_relaxed( - spi_engine->base + SPI_ENGINE_REG_SYNC_ID); - } - - spin_lock(&spi_engine->lock); - - if (pending & SPI_ENGINE_INT_CMD_ALMOST_EMPTY) { - if (!spi_engine_write_cmd_fifo(spi_engine)) - disable_int |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY; - } - - if (pending & SPI_ENGINE_INT_SDO_ALMOST_EMPTY) { - if (!spi_engine_write_tx_fifo(spi_engine)) - disable_int |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY; - } - - if (pending & (SPI_ENGINE_INT_SDI_ALMOST_FULL | SPI_ENGINE_INT_SYNC)) { - if (!spi_engine_read_rx_fifo(spi_engine)) - disable_int |= SPI_ENGINE_INT_SDI_ALMOST_FULL; - } - - if (pending & SPI_ENGINE_INT_SYNC) { - if (spi_engine->msg && - spi_engine->completed_id == spi_engine->sync_id) { - - del_timer(&spi_engine->watchdog_timer); - - spi_engine_complete_message(master, 0); - - disable_int |= SPI_ENGINE_INT_SYNC; - } - } - - if (disable_int) { - spi_engine->int_enable &= ~disable_int; - writel_relaxed(spi_engine->int_enable, - spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); - } - - spin_unlock(&spi_engine->lock); - - return IRQ_HANDLED; -} - -static void spi_engine_timeout(struct timer_list *t) -{ - struct spi_engine *spi_engine = from_timer(spi_engine, t, watchdog_timer); - struct spi_master *master = spi_engine->master; - - spin_lock(&spi_engine->lock); - if (spi_engine->msg) { - dev_err(&master->dev, "Timeout occured while waiting for transfer to complete. Hardware is probably broken.\n"); - spi_engine_complete_message(master, -ETIMEDOUT); - } - spin_unlock(&spi_engine->lock); -} - -static int spi_engine_transfer_one_message(struct spi_master *master, - struct spi_message *msg) -{ - struct spi_engine_program p_dry, *p; - struct spi_engine *spi_engine = spi_master_get_devdata(master); - unsigned int int_enable = 0; - unsigned long flags; - size_t size; - - del_timer_sync(&spi_engine->watchdog_timer); - - p_dry.length = 0; - spi_engine_compile_message(spi_engine, msg, true, &p_dry); - - size = sizeof(*p->instructions) * (p_dry.length + 1); - p = kzalloc(sizeof(*p) + size, GFP_KERNEL); - if (!p) - return -ENOMEM; - spi_engine_compile_message(spi_engine, msg, false, p); - - spin_lock_irqsave(&spi_engine->lock, flags); - spi_engine->sync_id = (spi_engine->sync_id + 1) & 0xff; - spi_engine_program_add_cmd(p, false, - SPI_ENGINE_CMD_SYNC(spi_engine->sync_id)); - - spi_engine->msg = msg; - spi_engine->p = p; - - spi_engine->cmd_buf = p->instructions; - spi_engine->cmd_length = p->length; - if (spi_engine_write_cmd_fifo(spi_engine)) - int_enable |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY; - - spi_engine_tx_next(spi_engine); - if (spi_engine_write_tx_fifo(spi_engine)) - int_enable |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY; - - spi_engine_rx_next(spi_engine); - if (spi_engine->rx_length != 0) - int_enable |= SPI_ENGINE_INT_SDI_ALMOST_FULL; - - int_enable |= SPI_ENGINE_INT_SYNC; - - writel_relaxed(int_enable, - spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); - spi_engine->int_enable = int_enable; - spin_unlock_irqrestore(&spi_engine->lock, flags); - - mod_timer(&spi_engine->watchdog_timer, jiffies + 5*HZ); - - return 0; -} - -static int spi_engine_probe(struct platform_device *pdev) -{ - struct spi_engine *spi_engine; - struct spi_master *master; - unsigned int version; - struct resource *res; - int irq; - int ret; - - irq = platform_get_irq(pdev, 0); - if (irq <= 0) - return -ENXIO; - - spi_engine = devm_kzalloc(&pdev->dev, sizeof(*spi_engine), GFP_KERNEL); - if (!spi_engine) - return -ENOMEM; - - master = spi_alloc_master(&pdev->dev, 0); - if (!master) - return -ENOMEM; - - spi_master_set_devdata(master, spi_engine); - spi_engine->master = master; - - spin_lock_init(&spi_engine->lock); - - spi_engine->clk = devm_clk_get(&pdev->dev, "s_axi_aclk"); - if (IS_ERR(spi_engine->clk)) { - ret = PTR_ERR(spi_engine->clk); - goto err_put_master; - } - - spi_engine->ref_clk = devm_clk_get(&pdev->dev, "spi_clk"); - if (IS_ERR(spi_engine->ref_clk)) { - ret = PTR_ERR(spi_engine->ref_clk); - goto err_put_master; - } - - ret = clk_prepare_enable(spi_engine->clk); - if (ret) - goto err_put_master; - - ret = clk_prepare_enable(spi_engine->ref_clk); - if (ret) - goto err_clk_disable; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - spi_engine->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(spi_engine->base)) { - ret = PTR_ERR(spi_engine->base); - goto err_ref_clk_disable; - } - - version = readl(spi_engine->base + SPI_ENGINE_REG_VERSION); - if (SPI_ENGINE_VERSION_MAJOR(version) != 1) { - dev_err(&pdev->dev, "Unsupported peripheral version %u.%u.%c\n", - SPI_ENGINE_VERSION_MAJOR(version), - SPI_ENGINE_VERSION_MINOR(version), - SPI_ENGINE_VERSION_PATCH(version)); - ret = -ENODEV; - goto err_ref_clk_disable; - } - - writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_RESET); - writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); - writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); - - ret = request_irq(irq, spi_engine_irq, 0, pdev->name, master); - if (ret) - goto err_ref_clk_disable; - - master->dev.of_node = pdev->dev.of_node; - master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_3WIRE; - master->max_speed_hz = clk_get_rate(spi_engine->ref_clk) / 2; - master->transfer_one_message = spi_engine_transfer_one_message; - master->num_chipselect = 8; - - timer_setup(&spi_engine->watchdog_timer, spi_engine_timeout, 0); - - ret = spi_register_master(master); - if (ret) - goto err_free_irq; - - platform_set_drvdata(pdev, master); - - return 0; -err_free_irq: - free_irq(irq, master); -err_ref_clk_disable: - clk_disable_unprepare(spi_engine->ref_clk); -err_clk_disable: - clk_disable_unprepare(spi_engine->clk); -err_put_master: - spi_master_put(master); - return ret; -} - -static int spi_engine_remove(struct platform_device *pdev) -{ - struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); - struct spi_engine *spi_engine = spi_master_get_devdata(master); - int irq = platform_get_irq(pdev, 0); - - spi_unregister_master(master); - - free_irq(irq, master); - - spi_master_put(master); - - writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); - writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); - writel_relaxed(0x01, spi_engine->base + SPI_ENGINE_REG_RESET); - - clk_disable_unprepare(spi_engine->ref_clk); - clk_disable_unprepare(spi_engine->clk); - - return 0; -} - -static const struct of_device_id spi_engine_match_table[] = { - { .compatible = "adi,axi-spi-engine-1.00.a" }, - { }, -}; -MODULE_DEVICE_TABLE(of, spi_engine_match_table); - -static struct platform_driver spi_engine_driver = { - .probe = spi_engine_probe, - .remove = spi_engine_remove, - .driver = { - .name = "spi-engine", - .of_match_table = spi_engine_match_table, - }, -}; -module_platform_driver(spi_engine_driver); - -MODULE_AUTHOR("Lars-Peter Clausen "); -MODULE_DESCRIPTION("Analog Devices SPI engine peripheral driver"); -MODULE_LICENSE("GPL"); +/* + * SPI-Engine SPI controller driver + * Copyright 2015 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPI_ENGINE_VERSION_MAJOR(x) ((x >> 16) & 0xff) +#define SPI_ENGINE_VERSION_MINOR(x) ((x >> 8) & 0xff) +#define SPI_ENGINE_VERSION_PATCH(x) (x & 0xff) + +#define SPI_ENGINE_REG_VERSION 0x00 + +#define SPI_ENGINE_REG_RESET 0x40 + +#define SPI_ENGINE_REG_INT_ENABLE 0x80 +#define SPI_ENGINE_REG_INT_PENDING 0x84 +#define SPI_ENGINE_REG_INT_SOURCE 0x88 + +#define SPI_ENGINE_REG_SYNC_ID 0xc0 + +#define SPI_ENGINE_REG_CMD_FIFO_ROOM 0xd0 +#define SPI_ENGINE_REG_SDO_FIFO_ROOM 0xd4 +#define SPI_ENGINE_REG_SDI_FIFO_LEVEL 0xd8 + +#define SPI_ENGINE_REG_CMD_FIFO 0xe0 +#define SPI_ENGINE_REG_SDO_DATA_FIFO 0xe4 +#define SPI_ENGINE_REG_SDI_DATA_FIFO 0xe8 +#define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK 0xec + +#define SPI_ENGINE_REG_OFFLOAD_CTRL(x) (0x100 + (0x20 * x)) +#define SPI_ENGINE_REG_OFFLOAD_STATUS(x) (0x104 + (0x20 * x)) +#define SPI_ENGINE_REG_OFFLOAD_RESET(x) (0x108 + (0x20 * x)) +#define SPI_ENGINE_REG_OFFLOAD_CMD_MEM(x) (0x110 + (0x20 * x)) +#define SPI_ENGINE_REG_OFFLOAD_SDO_MEM(x) (0x114 + (0x20 * x)) + +#define SPI_ENGINE_INT_CMD_ALMOST_EMPTY BIT(0) +#define SPI_ENGINE_INT_SDO_ALMOST_EMPTY BIT(1) +#define SPI_ENGINE_INT_SDI_ALMOST_FULL BIT(2) +#define SPI_ENGINE_INT_SYNC BIT(3) + +#define SPI_ENGINE_OFFLOAD_CTRL_ENABLE BIT(0) +#define SPI_ENGINE_OFFLOAD_STATUS_ENABLED BIT(0) + +#define SPI_ENGINE_CONFIG_CPHA BIT(0) +#define SPI_ENGINE_CONFIG_CPOL BIT(1) +#define SPI_ENGINE_CONFIG_3WIRE BIT(2) + +#define SPI_ENGINE_INST_TRANSFER 0x0 +#define SPI_ENGINE_INST_ASSERT 0x1 +#define SPI_ENGINE_INST_WRITE 0x2 +#define SPI_ENGINE_INST_MISC 0x3 + +#define SPI_ENGINE_CMD_REG_CLK_DIV 0x0 +#define SPI_ENGINE_CMD_REG_CONFIG 0x1 +#define SPI_ENGINE_CMD_REG_WORD_LENGTH 0x2 + +#define SPI_ENGINE_MISC_SYNC 0x0 +#define SPI_ENGINE_MISC_SLEEP 0x1 + +#define SPI_ENGINE_TRANSFER_WRITE 0x1 +#define SPI_ENGINE_TRANSFER_READ 0x2 + +#define SPI_ENGINE_CMD(inst, arg1, arg2) \ + (((inst) << 12) | ((arg1) << 8) | (arg2)) + +#define SPI_ENGINE_CMD_TRANSFER(flags, n) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_TRANSFER, (flags), (n)) +#define SPI_ENGINE_CMD_ASSERT(delay, cs) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_ASSERT, (delay), (cs)) +#define SPI_ENGINE_CMD_WRITE(reg, val) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_WRITE, (reg), (val)) +#define SPI_ENGINE_CMD_SLEEP(delay) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SLEEP, (delay)) +#define SPI_ENGINE_CMD_SYNC(id) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SYNC, (id)) + +struct spi_engine_program { + unsigned int length; + uint16_t instructions[]; +}; + +struct spi_engine { + struct clk *clk; + struct clk *ref_clk; + + struct spi_master *master; + + spinlock_t lock; + + void __iomem *base; + + struct spi_message *msg; + struct spi_engine_program *p; + unsigned cmd_length; + const uint16_t *cmd_buf; + + struct spi_transfer *tx_xfer; + unsigned int tx_length; + const uint8_t *tx_buf; + + struct spi_transfer *rx_xfer; + unsigned int rx_length; + uint8_t *rx_buf; + + unsigned int sync_id; + unsigned int completed_id; + + unsigned int int_enable; + + struct timer_list watchdog_timer; + unsigned int word_length; +}; + +static void spi_engine_program_add_cmd(struct spi_engine_program *p, + bool dry, uint16_t cmd) +{ + if (!dry) + p->instructions[p->length] = cmd; + p->length++; +} + +static unsigned int spi_engine_get_config(struct spi_device *spi) +{ + unsigned int config = 0; + + if (spi->mode & SPI_CPOL) + config |= SPI_ENGINE_CONFIG_CPOL; + if (spi->mode & SPI_CPHA) + config |= SPI_ENGINE_CONFIG_CPHA; + if (spi->mode & SPI_3WIRE) + config |= SPI_ENGINE_CONFIG_3WIRE; + + return config; +} + +static unsigned int spi_engine_get_clk_div(struct spi_engine *spi_engine, + struct spi_device *spi, struct spi_transfer *xfer) +{ + unsigned int clk_div; + unsigned int speed; + + if (xfer->speed_hz) + speed = xfer->speed_hz; + else + speed = spi->max_speed_hz; + + clk_div = DIV_ROUND_UP(clk_get_rate(spi_engine->ref_clk), + speed * 2); + if (clk_div > 255) + clk_div = 255; + else if (clk_div > 0) + clk_div -= 1; + + return clk_div; +} + +static unsigned int spi_engine_get_word_length(struct spi_engine *spi_engine, + struct spi_device *spi, struct spi_transfer *xfer) +{ + /* if bits_per_word is 0 this will default to 8 bit words */ + if (xfer->bits_per_word) + return xfer->bits_per_word; + else if (spi->bits_per_word) + return spi->bits_per_word; + else + return 8; +} + +static void spi_engine_update_xfer_len(struct spi_engine *spi_engine, + struct spi_transfer *xfer) +{ + unsigned int word_length = spi_engine->word_length; + unsigned int word_len_bytes = word_length / 8; + + if ((xfer->len * 8) < word_length) + xfer->len = 1; + else + xfer->len = DIV_ROUND_UP(xfer->len, word_len_bytes); +} + +static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry, + struct spi_transfer *xfer) +{ + unsigned int len = xfer->len; + + while (len) { + unsigned int n = min(len, 256U); + unsigned int flags = 0; + + if (xfer->tx_buf) + flags |= SPI_ENGINE_TRANSFER_WRITE; + if (xfer->rx_buf) + flags |= SPI_ENGINE_TRANSFER_READ; + + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_TRANSFER(flags, n - 1)); + len -= n; + } +} + +static void spi_engine_gen_sleep(struct spi_engine_program *p, bool dry, + struct spi_engine *spi_engine, unsigned int clk_div, unsigned int delay) +{ + unsigned int spi_clk = clk_get_rate(spi_engine->ref_clk); + unsigned int t; + + if (delay == 0) + return; + + t = DIV_ROUND_UP(delay * spi_clk, (clk_div + 1) * 2); + /* spi_clk is in Hz while delay is usec, a division is required */ + t /= 1000000; + while (t) { + unsigned int n = min(t, 256U); + + spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_SLEEP(n - 1)); + t -= n; + } +} + +static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry, + struct spi_device *spi, bool assert) +{ + unsigned int mask = 0xff; + + if (assert) + mask ^= BIT(spi->chip_select); + + spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_ASSERT(1, mask)); +} + +static int spi_engine_compile_message(struct spi_engine *spi_engine, + struct spi_message *msg, bool dry, struct spi_engine_program *p) +{ + struct spi_device *spi = msg->spi; + struct spi_transfer *xfer; + int clk_div, new_clk_div; + int word_len, new_word_len; + bool cs_change = true; + + clk_div = -1; + word_len = -1; + + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CONFIG, + spi_engine_get_config(spi))); + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + new_clk_div = spi_engine_get_clk_div(spi_engine, spi, xfer); + if (new_clk_div != clk_div) { + clk_div = new_clk_div; + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV, + clk_div)); + } + + new_word_len = spi_engine_get_word_length(spi_engine, spi, xfer); + if (new_word_len != word_len) { + word_len = new_word_len; + spi_engine->word_length = word_len; + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_WORD_LENGTH, + word_len)); + } + + if (cs_change) + spi_engine_gen_cs(p, dry, spi, true); + + spi_engine_update_xfer_len(spi_engine, xfer); + spi_engine_gen_xfer(p, dry, xfer); + spi_engine_gen_sleep(p, dry, spi_engine, clk_div, + xfer->delay_usecs); + + cs_change = xfer->cs_change; + if (list_is_last(&xfer->transfer_list, &msg->transfers)) + cs_change = !cs_change; + + if (cs_change) + spi_engine_gen_cs(p, dry, spi, false); + } + + return 0; +} + +bool spi_engine_offload_supported(struct spi_device *spi) +{ + if (strcmp(spi->master->dev.parent->driver->name, "spi-engine") != 0) + return false; + + return true; +} +EXPORT_SYMBOL_GPL(spi_engine_offload_supported); + +int spi_engine_write_reg(struct spi_device *spi,unsigned int addr,unsigned int val) +{ + struct spi_master *master = spi->master; + struct spi_engine *spi_engine = spi_master_get_devdata(master); + + writel(val, spi_engine->base + (addr<<2)); + return 0; +} +EXPORT_SYMBOL_GPL(spi_engine_write_reg); + +void spi_engine_offload_enable(struct spi_device *spi, bool enable) +{ + struct spi_master *master = spi->master; + struct spi_engine *spi_engine = spi_master_get_devdata(master); + unsigned int reg; + + reg = readl(spi_engine->base + SPI_ENGINE_REG_OFFLOAD_CTRL(0)); + if (enable) + reg |= SPI_ENGINE_OFFLOAD_CTRL_ENABLE; + else + reg &= ~SPI_ENGINE_OFFLOAD_CTRL_ENABLE; + writel(reg, spi_engine->base + SPI_ENGINE_REG_OFFLOAD_CTRL(0)); +} +EXPORT_SYMBOL_GPL(spi_engine_offload_enable); + +int spi_engine_offload_load_msg(struct spi_device *spi, + struct spi_message *msg) +{ + struct spi_master *master = spi->master; + struct spi_engine *spi_engine = spi_master_get_devdata(master); + struct spi_engine_program p_dry; + struct spi_engine_program *p; + struct spi_transfer *xfer; + void __iomem *cmd_addr; + void __iomem *sdo_addr; + const uint8_t *buf; + unsigned int i, j; + size_t size; + + msg->spi = spi; + + p_dry.length = 0; + spi_engine_compile_message(spi_engine, msg, true, &p_dry); + + size = sizeof(*p->instructions) * (p_dry.length + 2); + + p = kzalloc(sizeof(*p) + size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + cmd_addr = spi_engine->base + SPI_ENGINE_REG_OFFLOAD_CMD_MEM(0); + sdo_addr = spi_engine->base + SPI_ENGINE_REG_OFFLOAD_SDO_MEM(0); + + spi_engine_compile_message(spi_engine, msg, false, p); + spi_engine_program_add_cmd(p, false, SPI_ENGINE_CMD_SYNC(0)); + + writel(1, spi_engine->base + SPI_ENGINE_REG_OFFLOAD_RESET(0)); + writel(0, spi_engine->base + SPI_ENGINE_REG_OFFLOAD_RESET(0)); + j = 0; + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + if (!xfer->tx_buf) + continue; + buf = xfer->tx_buf; + for (i = 0; i < xfer->len; i++, j++) + writel(buf[i], sdo_addr); + } + + for (i = 0; i < p->length; i++) + writel(p->instructions[i], cmd_addr); + + kfree(p); + + return 0; +} +EXPORT_SYMBOL_GPL(spi_engine_offload_load_msg); + +static void spi_engine_xfer_next(struct spi_engine *spi_engine, + struct spi_transfer **_xfer) +{ + struct spi_message *msg = spi_engine->msg; + struct spi_transfer *xfer = *_xfer; + + if (!xfer) { + xfer = list_first_entry(&msg->transfers, + struct spi_transfer, transfer_list); + } else if (list_is_last(&xfer->transfer_list, &msg->transfers)) { + xfer = NULL; + } else { + xfer = list_next_entry(xfer, transfer_list); + } + + *_xfer = xfer; +} + +static void spi_engine_tx_next(struct spi_engine *spi_engine) +{ + struct spi_transfer *xfer = spi_engine->tx_xfer; + + do { + spi_engine_xfer_next(spi_engine, &xfer); + } while (xfer && !xfer->tx_buf); + + spi_engine->tx_xfer = xfer; + if (xfer) { + spi_engine->tx_length = xfer->len; + spi_engine->tx_buf = xfer->tx_buf; + } else { + spi_engine->tx_buf = NULL; + } +} + +static void spi_engine_rx_next(struct spi_engine *spi_engine) +{ + struct spi_transfer *xfer = spi_engine->rx_xfer; + + do { + spi_engine_xfer_next(spi_engine, &xfer); + } while (xfer && !xfer->rx_buf); + + spi_engine->rx_xfer = xfer; + if (xfer) { + spi_engine->rx_length = xfer->len; + spi_engine->rx_buf = xfer->rx_buf; + } else { + spi_engine->rx_buf = NULL; + } +} + +static bool spi_engine_write_cmd_fifo(struct spi_engine *spi_engine) +{ + void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_CMD_FIFO; + unsigned int n, m, i; + const uint16_t *buf; + + n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_CMD_FIFO_ROOM); + while (n && spi_engine->cmd_length) { + m = min(n, spi_engine->cmd_length); + buf = spi_engine->cmd_buf; + for (i = 0; i < m; i++) + writel_relaxed(buf[i], addr); + spi_engine->cmd_buf += m; + spi_engine->cmd_length -= m; + n -= m; + } + + return spi_engine->cmd_length != 0; +} + +static void spi_engine_read_buff(struct spi_engine *spi_engine, uint8_t m) +{ + void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDI_DATA_FIFO; + uint32_t val; + uint8_t bytes_len, i, j, *buf; + + bytes_len = DIV_ROUND_UP(spi_engine->word_length, 8); + buf = spi_engine->rx_buf; + + for (i = 0; i < (m * bytes_len); i += bytes_len) { + val = readl_relaxed(addr); + for (j = 0; j < bytes_len; j++) + buf[j] = val >> (8 * j); + buf += bytes_len; + } + + spi_engine->rx_buf += i; +} + +static void spi_engine_write_buff(struct spi_engine *spi_engine, uint8_t m) +{ + void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDO_DATA_FIFO; + uint8_t bytes_len, word_len, i, j; + uint32_t val = 0; + + bytes_len = DIV_ROUND_DOWN_ULL(spi_engine->word_length, 8); + word_len = spi_engine->word_length; + + for (i = 0; i < (m * bytes_len); i += bytes_len) { + for (j = 0; j < bytes_len; j++) + val |= spi_engine->tx_buf[i + j] << (word_len - 8 * (j + 1)); + writel_relaxed(val, addr); + val = 0; + } + + spi_engine->tx_buf += i; +} + +static bool spi_engine_write_tx_fifo(struct spi_engine *spi_engine) +{ + unsigned int n, m; + + n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDO_FIFO_ROOM); + while (n && spi_engine->tx_length) { + m = min(n, spi_engine->tx_length); + spi_engine_write_buff(spi_engine, m); + spi_engine->tx_length -= m; + n -= m; + if (spi_engine->tx_length == 0) + spi_engine_tx_next(spi_engine); + } + + return spi_engine->tx_length != 0; +} + +static bool spi_engine_read_rx_fifo(struct spi_engine *spi_engine) +{ + unsigned int n, m; + + n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDI_FIFO_LEVEL); + while (n && spi_engine->rx_length) { + m = min(n, spi_engine->rx_length); + spi_engine_read_buff(spi_engine, m); + spi_engine->rx_length -= m; + n -= m; + if (spi_engine->rx_length == 0) + spi_engine_rx_next(spi_engine); + } + + return spi_engine->rx_length != 0; +} + +static void spi_engine_complete_message(struct spi_master *master, int status) +{ + struct spi_engine *spi_engine = spi_master_get_devdata(master); + struct spi_message *msg = spi_engine->msg; + + kfree(spi_engine->p); + msg->status = status; + msg->actual_length = msg->frame_length; + spi_engine->msg = NULL; + spi_finalize_current_message(master); +} + +static irqreturn_t spi_engine_irq(int irq, void *devid) +{ + struct spi_master *master = devid; + struct spi_engine *spi_engine = spi_master_get_devdata(master); + unsigned int disable_int = 0; + unsigned int pending; + + pending = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + if (pending & SPI_ENGINE_INT_SYNC) { + writel_relaxed(SPI_ENGINE_INT_SYNC, + spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + spi_engine->completed_id = readl_relaxed( + spi_engine->base + SPI_ENGINE_REG_SYNC_ID); + } + + spin_lock(&spi_engine->lock); + + if (pending & SPI_ENGINE_INT_CMD_ALMOST_EMPTY) { + if (!spi_engine_write_cmd_fifo(spi_engine)) + disable_int |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY; + } + + if (pending & SPI_ENGINE_INT_SDO_ALMOST_EMPTY) { + if (!spi_engine_write_tx_fifo(spi_engine)) + disable_int |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY; + } + + if (pending & (SPI_ENGINE_INT_SDI_ALMOST_FULL | SPI_ENGINE_INT_SYNC)) { + if (!spi_engine_read_rx_fifo(spi_engine)) + disable_int |= SPI_ENGINE_INT_SDI_ALMOST_FULL; + } + + if (pending & SPI_ENGINE_INT_SYNC) { + if (spi_engine->msg && + spi_engine->completed_id == spi_engine->sync_id) { + + del_timer(&spi_engine->watchdog_timer); + + spi_engine_complete_message(master, 0); + + disable_int |= SPI_ENGINE_INT_SYNC; + } + } + + if (disable_int) { + spi_engine->int_enable &= ~disable_int; + writel_relaxed(spi_engine->int_enable, + spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + } + + spin_unlock(&spi_engine->lock); + + return IRQ_HANDLED; +} + +static void spi_engine_timeout(struct timer_list *t) +{ + struct spi_engine *spi_engine = from_timer(spi_engine, t, watchdog_timer); + struct spi_master *master = spi_engine->master; + + spin_lock(&spi_engine->lock); + if (spi_engine->msg) { + dev_err(&master->dev, "Timeout occured while waiting for transfer to complete. Hardware is probably broken.\n"); + spi_engine_complete_message(master, -ETIMEDOUT); + } + spin_unlock(&spi_engine->lock); +} + +static int spi_engine_transfer_one_message(struct spi_master *master, + struct spi_message *msg) +{ + struct spi_engine_program p_dry, *p; + struct spi_engine *spi_engine = spi_master_get_devdata(master); + unsigned int int_enable = 0; + unsigned long flags; + size_t size; + + del_timer_sync(&spi_engine->watchdog_timer); + + p_dry.length = 0; + spi_engine_compile_message(spi_engine, msg, true, &p_dry); + + size = sizeof(*p->instructions) * (p_dry.length + 1); + p = kzalloc(sizeof(*p) + size, GFP_KERNEL); + if (!p) + return -ENOMEM; + spi_engine_compile_message(spi_engine, msg, false, p); + + spin_lock_irqsave(&spi_engine->lock, flags); + spi_engine->sync_id = (spi_engine->sync_id + 1) & 0xff; + spi_engine_program_add_cmd(p, false, + SPI_ENGINE_CMD_SYNC(spi_engine->sync_id)); + + spi_engine->msg = msg; + spi_engine->p = p; + + spi_engine->cmd_buf = p->instructions; + spi_engine->cmd_length = p->length; + if (spi_engine_write_cmd_fifo(spi_engine)) + int_enable |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY; + + spi_engine_tx_next(spi_engine); + if (spi_engine_write_tx_fifo(spi_engine)) + int_enable |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY; + + spi_engine_rx_next(spi_engine); + if (spi_engine->rx_length != 0) + int_enable |= SPI_ENGINE_INT_SDI_ALMOST_FULL; + + int_enable |= SPI_ENGINE_INT_SYNC; + + writel_relaxed(int_enable, + spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + spi_engine->int_enable = int_enable; + spin_unlock_irqrestore(&spi_engine->lock, flags); + + mod_timer(&spi_engine->watchdog_timer, jiffies + 5*HZ); + + return 0; +} + +static int spi_engine_probe(struct platform_device *pdev) +{ + struct spi_engine *spi_engine; + struct spi_master *master; + unsigned int version; + struct resource *res; + int irq; + int ret; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return -ENXIO; + + spi_engine = devm_kzalloc(&pdev->dev, sizeof(*spi_engine), GFP_KERNEL); + if (!spi_engine) + return -ENOMEM; + + master = spi_alloc_master(&pdev->dev, 0); + if (!master) + return -ENOMEM; + + spi_master_set_devdata(master, spi_engine); + spi_engine->master = master; + + spin_lock_init(&spi_engine->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spi_engine->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(spi_engine->base)) { + ret = PTR_ERR(spi_engine->base); + goto err_put_master; + } + + version = readl(spi_engine->base + SPI_ENGINE_REG_VERSION); + /*if (SPI_ENGINE_VERSION_MAJOR(version) != 1) { + dev_err(&pdev->dev, "Unsupported peripheral version %u.%u.%c\n", + SPI_ENGINE_VERSION_MAJOR(version), + SPI_ENGINE_VERSION_MINOR(version), + SPI_ENGINE_VERSION_PATCH(version)); + ret = -ENODEV; + goto err_put_master; + }*/ + + spi_engine->clk = devm_clk_get(&pdev->dev, "s_axi_aclk"); + if (IS_ERR(spi_engine->clk)) { + ret = PTR_ERR(spi_engine->clk); + goto err_put_master; + } + + spi_engine->ref_clk = devm_clk_get(&pdev->dev, "spi_clk"); + if (IS_ERR(spi_engine->ref_clk)) { + ret = PTR_ERR(spi_engine->ref_clk); + goto err_put_master; + } + + ret = clk_prepare_enable(spi_engine->clk); + if (ret) + goto err_put_master; + + ret = clk_prepare_enable(spi_engine->ref_clk); + if (ret) + goto err_clk_disable; + + writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_RESET); + writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + + ret = request_irq(irq, spi_engine_irq, 0, pdev->name, master); + if (ret) + goto err_ref_clk_disable; + + master->dev.of_node = pdev->dev.of_node; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_3WIRE; + master->max_speed_hz = clk_get_rate(spi_engine->ref_clk) / 2; + master->transfer_one_message = spi_engine_transfer_one_message; + master->num_chipselect = 8; + + timer_setup(&spi_engine->watchdog_timer, spi_engine_timeout, 0); + + ret = spi_register_master(master); + if (ret) + goto err_free_irq; + + platform_set_drvdata(pdev, master); + + return 0; +err_free_irq: + free_irq(irq, master); +err_ref_clk_disable: + clk_disable_unprepare(spi_engine->ref_clk); +err_clk_disable: + clk_disable_unprepare(spi_engine->clk); +err_put_master: + spi_master_put(master); + return ret; +} + +static int spi_engine_remove(struct platform_device *pdev) +{ + struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); + struct spi_engine *spi_engine = spi_master_get_devdata(master); + int irq = platform_get_irq(pdev, 0); + + spi_unregister_master(master); + + free_irq(irq, master); + + spi_master_put(master); + + writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + writel_relaxed(0x01, spi_engine->base + SPI_ENGINE_REG_RESET); + + clk_disable_unprepare(spi_engine->ref_clk); + clk_disable_unprepare(spi_engine->clk); + + return 0; +} + +static const struct of_device_id spi_engine_match_table[] = { + { .compatible = "adi,axi-spi-engine-1.00.a" }, + { }, +}; +MODULE_DEVICE_TABLE(of, spi_engine_match_table); + +static struct platform_driver spi_engine_driver = { + .probe = spi_engine_probe, + .remove = spi_engine_remove, + .driver = { + .name = "spi-engine", + .of_match_table = spi_engine_match_table, + }, +}; +module_platform_driver(spi_engine_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices SPI engine peripheral driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/spi/spi-engine.h b/include/linux/spi/spi-engine.h index 9dbf149adf18..49d92085cff8 100644 --- a/include/linux/spi/spi-engine.h +++ b/include/linux/spi/spi-engine.h @@ -15,6 +15,7 @@ bool spi_engine_offload_supported(struct spi_device *spi); void spi_engine_offload_enable(struct spi_device *spi, bool enable); int spi_engine_offload_load_msg(struct spi_device *spi, struct spi_message *msg); +int spi_engine_write_reg(struct spi_device *spi,unsigned int addr,unsigned int val); #else @@ -33,6 +34,10 @@ static inline int spi_engine_offload_load_msg(struct spi_device *spi, return -ENODEV; } +static inline int spi_engine_write_reg(struct spi_device *spi,unsigned int addr,unsigned int val) +{ +} + #endif #endif -- 2.17.1