diff --git a/Documentation/ABI/testing/sysfs-bus-iio-adc-ad485x b/Documentation/ABI/testing/sysfs-bus-iio-adc-ad485x new file mode 100644 index 00000000000000..80aaef4eb750f2 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-adc-ad485x @@ -0,0 +1,14 @@ +What: /sys/bus/iio/devices/iio:deviceX/packet_format_available +KernelVersion: +Contact: linux-iio@vger.kernel.org +Description: + Packet sizes on the CMOS or LVDS conversion data output bus. + Reading this returns the valid values that can be written to the + packet_format. + +What: /sys/bus/iio/devices/iio:deviceX/packet_format +KernelVersion: +Contact: linux-iio@vger.kernel.org +Description: + This attribute configures the packet size. + Reading returns the actual size used. diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad485x.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad485x.yaml new file mode 100644 index 00000000000000..5f5bdfa9522b26 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad485x.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2022 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad485x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD485X DAS family device driver + +maintainers: + - Sergiu Cuciurean + - Dragos Bogdan + - Antoniu Miclaus + +description: | + Analog Devices AD485X DAS family + + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4858.pdf + +properties: + compatible: + enum: + - adi,ad4858 + - adi,ad4857 + - adi,ad4856 + - adi,ad4855 + - adi,ad4854 + - adi,ad4853 + - adi,ad4852 + - adi,ad4851 + - adi,ad4858i + + reg: + maxItems: 1 + + vcc-supply: true + + vdd-supply: true + + vddh-supply: true + + vio-supply: true + + pwms: + maxItems: 1 + + io-backends: + maxItems: 1 + + spi-max-frequency: + maximum: 100000000 + +required: + - compatible + - reg + - vcc-supply + - vdd-supply + - vddh-supply + - vio-supply + - pwms + +unevaluatedProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0{ + compatible = "adi,ad4858"; + reg = <0>; + spi-max-frequency = <10000000>; + vcc-supply = <&vcc>; + vdd-supply = <&vdd>; + vddh-supply = <&vddh>; + vio-supply = <&vio>; + pwms = <&pwm_gen 0 0>; + io-backends = <&iio_backend>; + }; + }; +... diff --git a/arch/arm/boot/dts/xilinx/zynq-zed-ad485x.dts b/arch/arm/boot/dts/xilinx/zynq-zed-ad485x.dts new file mode 100644 index 00000000000000..bd9b28627d424a --- /dev/null +++ b/arch/arm/boot/dts/xilinx/zynq-zed-ad485x.dts @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +#include "zynq-zed.dts" + +#include + +/ { + clocks { + axi_clk: clock@0 { + compatible = "fixed-clock"; + clock-frequency = <100000000>; + clock-output-names = "axi-clk"; + #clock-cells = <0>; + }; + }; + + vcc: regulator-vcc { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <7250000>; + regulator-max-microvolt = <7250000>; + regulator-always-on; + }; + + vdd: regulator-vdd { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; + + vddh: regulator-vddh { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-always-on; + }; + + vio: regulator-vio { + compatible = "regulator-fixed"; + regulator-name = "fixed-supply"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + fpga-axi@0 { + compatible = "simple-bus"; + #address-cells = <0x1>; + #size-cells = <0x1>; + ranges; + + rx_dma: dmac@43e00000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x43E00000 0x1000>; + #dma-cells = <1>; + interrupt-parent = <&intc>; + interrupts = <0 54 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15>; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma_channel: 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>; + }; + }; + }; + + ref_clk: clk@0x44000000 { + compatible = "adi,axi-clkgen-2.00.a"; + reg = <0x44000000 0x10000>; + #clock-cells = <0>; + clocks = <&clkc 15>, <&clkc 16>; + clock-names = "s_axi_aclk", "clkin1"; + clock-output-names = "ref_clk"; + }; + + axi_pwm_gen: pwm@0x43d00000 { + compatible = "adi,axi-pwmgen"; + reg = <0x43d00000 0x1000>; + label = "cnv"; + #pwm-cells = <2>; + clocks = <&ref_clk>; + }; + + iio_backend: axi_adc@43c00000 { + compatible = "adi,axi-adc-10.0.a"; + reg = <0x43c00000 0x10000>; + dmas = <&rx_dma 0>; + dma-names = "rx"; + clocks = <&axi_clk>; + spibus-connected = <&ad485x>; + }; + }; +}; + +&spi0 { + status = "okay"; + + ad485x: adc@0{ + compatible = "adi,ad4857"; + reg = <0>; + spi-max-frequency = <10000000>; + pwms = <&axi_pwm_gen 0 0>; + pwm-names = "cnv"; + vcc-supply = <&vcc>; + vdd-supply = <&vdd>; + vddh-supply = <&vddh>; + vio-supply = <&vio>; + io-backends = <&iio_backend>; + }; +}; diff --git a/arch/arm/configs/zynq_defconfig b/arch/arm/configs/zynq_defconfig new file mode 100644 index 00000000000000..868489f11e26d9 --- /dev/null +++ b/arch/arm/configs/zynq_defconfig @@ -0,0 +1,181 @@ +CONFIG_SYSVIPC=y +CONFIG_USELIB=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=15 +CONFIG_CGROUPS=y +CONFIG_USER_NS=y +CONFIG_BLK_DEV_INITRD=y +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_XZ is not set +# CONFIG_RD_LZO is not set +# CONFIG_RD_LZ4 is not set +CONFIG_PERF_EVENTS=y +CONFIG_ARCH_ZYNQ=y +CONFIG_PL310_ERRATA_588369=y +CONFIG_PL310_ERRATA_727915=y +CONFIG_PL310_ERRATA_769419=y +# CONFIG_ARM_ERRATA_643719 is not set +CONFIG_ARM_ERRATA_754322=y +CONFIG_ARM_ERRATA_754327=y +CONFIG_ARM_ERRATA_764369=y +CONFIG_ARM_ERRATA_775420=y +CONFIG_SMP=y +CONFIG_SCHED_MC=y +CONFIG_SCHED_SMT=y +CONFIG_HIGHMEM=y +CONFIG_CMDLINE="console=ttyPS0,115200n8 root=/dev/ram rw initrd=0x00800000,16M earlyprintk mtdparts=physmap-flash.0:512K(nor-fsbl),512K(nor-u-boot),5M(nor-linux),9M(nor-user),1M(nor-scratch),-(nor-rootfs)" +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_MENU=y +CONFIG_ARM_ZYNQ_CPUIDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_GCC_PLUGINS is not set +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +# CONFIG_COMPACTION is not set +CONFIG_CMA=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_RARP=y +CONFIG_NET_IPIP=m +CONFIG_VLAN_8021Q=m +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_CONNECTOR=y +CONFIG_MTD=y +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_CFI=y +CONFIG_MTD_CFI_AMDSTD=y +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_OF_OVERLAY=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +# CONFIG_BLK_DEV_BSG is not set +CONFIG_NETDEVICES=y +CONFIG_MACSEC=y +CONFIG_TUN=y +# CONFIG_NET_VENDOR_BROADCOM is not set +CONFIG_MACB=y +# CONFIG_NET_VENDOR_CIRRUS is not set +# CONFIG_NET_VENDOR_FARADAY is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +CONFIG_MARVELL_PHY=y +CONFIG_XILINX_GMII2RGMII=y +CONFIG_MDIO_BITBANG=y +# CONFIG_WLAN is not set +CONFIG_INPUT_SPARSEKMAP=y +CONFIG_INPUT_EVDEV=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_XILINX_PS_UART=y +CONFIG_SERIAL_XILINX_PS_UART_CONSOLE=y +# CONFIG_HW_RANDOM is not set +CONFIG_SPI=y +CONFIG_SPI_BITBANG=y +CONFIG_SPI_CADENCE=y +CONFIG_GPIOLIB=y +CONFIG_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_XILINX_WATCHDOG=y +CONFIG_CADENCE_WATCHDOG=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_HIDRAW=y +CONFIG_HID_ACRUX=y +CONFIG_HID_ACRUX_FF=y +CONFIG_HID_DRAGONRISE=y +CONFIG_HID_EMS_FF=y +CONFIG_HID_KEYTOUCH=y +CONFIG_HID_KYE=y +CONFIG_HID_WALTOP=y +CONFIG_HID_GYRATION=y +CONFIG_HID_TWINHAN=y +CONFIG_HID_LCPOWER=y +CONFIG_HID_MULTITOUCH=y +CONFIG_HID_ORTEK=y +CONFIG_HID_PANTHERLORD=y +CONFIG_PANTHERLORD_FF=y +CONFIG_HID_PETALYNX=y +CONFIG_HID_PICOLCD=y +CONFIG_HID_PRIMAX=y +CONFIG_HID_SPEEDLINK=y +CONFIG_HID_SUNPLUS=y +CONFIG_HID_GREENASIA=y +CONFIG_GREENASIA_FF=y +CONFIG_HID_SMARTJOYPLUS=y +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_HID_TOPSEED=y +CONFIG_HID_ZEROPLUS=y +CONFIG_ZEROPLUS_FF=y +CONFIG_HID_ZYDACRON=y +CONFIG_MMC=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_OF_ARASAN=y +CONFIG_RTC_CLASS=y +CONFIG_DMADEVICES=y +CONFIG_AXI_DMAC=y +CONFIG_PL330_DMA=y +CONFIG_UIO=y +CONFIG_UIO_PDRV_GENIRQ=y +CONFIG_UIO_DMEM_GENIRQ=y +CONFIG_COMMON_CLK_AXI_CLKGEN=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_MEMORY=y +CONFIG_IIO=y +CONFIG_AD485X=y +CONFIG_ADI_AXI_ADC=y +CONFIG_PWM=y +CONFIG_FPGA=y +CONFIG_FPGA_MGR_ZYNQ_FPGA=y +CONFIG_FPGA_BRIDGE=y +CONFIG_FPGA_REGION=y +CONFIG_OF_FPGA_REGION=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +# CONFIG_DNOTIFY is not set +CONFIG_FUSE_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=128 +CONFIG_DEBUG_FS=y +CONFIG_RCU_CPU_STALL_TIMEOUT=60 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 8db68b80b39156..1c2e080341ee3a 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -36,6 +36,18 @@ config AD4130 To compile this driver as a module, choose M here: the module will be called ad4130. +config AD485X + tristate "Analog Device AD485x DAS Driver" + depends on SPI + select REGMAP_SPI + select IIO_BACKEND + help + Say yes here to build support for Analog Devices AD485x high speed + data acquisition system (DAS). + + To compile this driver as a module, choose M here: the module will be + called ad485x. + config AD7091R tristate diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index edb32ce2af026b..3946b0ef3bbd31 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o obj-$(CONFIG_AD4130) += ad4130.o +obj-$(CONFIG_AD485X) += ad485x.o obj-$(CONFIG_AD7091R) += ad7091r-base.o obj-$(CONFIG_AD7091R5) += ad7091r5.o obj-$(CONFIG_AD7091R8) += ad7091r8.o diff --git a/drivers/iio/adc/ad485x.c b/drivers/iio/adc/ad485x.c new file mode 100644 index 00000000000000..aae019c2700c1d --- /dev/null +++ b/drivers/iio/adc/ad485x.c @@ -0,0 +1,1073 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD485x DAS driver + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD485X_REG_INTERFACE_CONFIG_A 0x00 +#define AD485X_REG_INTERFACE_CONFIG_B 0x01 +#define AD485X_REG_PRODUCT_ID_L 0x04 +#define AD485X_REG_PRODUCT_ID_H 0x05 +#define AD485X_REG_DEVICE_CTRL 0x25 +#define AD485X_REG_PACKET 0x26 + +#define AD485X_REG_CH_CONFIG_BASE 0x2A +#define AD485X_REG_CHX_SOFTSPAN(ch) ((0x12 * (ch)) + AD485X_REG_CH_CONFIG_BASE) +#define AD485X_REG_CHX_OFFSET(ch) (AD485X_REG_CHX_SOFTSPAN(ch) + 0x01) +#define AD485X_REG_CHX_OFFSET_LSB(ch) AD485X_REG_CHX_OFFSET(ch) +#define AD485X_REG_CHX_OFFSET_MID(ch) (AD485X_REG_CHX_OFFSET_LSB(ch) + 0x01) +#define AD485X_REG_CHX_OFFSET_MSB(ch) (AD485X_REG_CHX_OFFSET_MID(ch) + 0x01) +#define AD485X_REG_CHX_GAIN(ch) (AD485X_REG_CHX_OFFSET(ch) + 0x03) +#define AD485X_REG_CHX_GAIN_LSB(ch) AD485X_REG_CHX_GAIN(ch) +#define AD485X_REG_CHX_GAIN_MSB(ch) (AD485X_REG_CHX_GAIN(ch) + 0x01) +#define AD485X_REG_CHX_PHASE(ch) (AD485X_REG_CHX_GAIN(ch) + 0x02) +#define AD485X_REG_CHX_PHASE_LSB(ch) AD485X_REG_CHX_PHASE(ch) +#define AD485X_REG_CHX_PHASE_MSB(ch) (AD485X_REG_CHX_PHASE_LSB(ch) + 0x01) + +#define AD485X_REG_TESTPAT_0(c) (0x38 + (c) * 0x12) +#define AD485X_REG_TESTPAT_1(c) (0x39 + (c) * 0x12) +#define AD485X_REG_TESTPAT_2(c) (0x3A + (c) * 0x12) +#define AD485X_REG_TESTPAT_3(c) (0x3B + (c) * 0x12) + +#define AD485X_SW_RESET (BIT(7) | BIT(0)) +#define AD485X_SDO_ENABLE BIT(4) +#define AD485X_SINGLE_INSTRUCTION BIT(7) +#define AD485X_ECHO_CLOCK_MODE BIT(0) + +#define AD485X_PACKET_FORMAT_0 0 +#define AD485X_PACKET_FORMAT_1 1 +#define AD485X_PACKET_FORMAT_MASK GENMASK(1, 0) +#define AD485X_OS_EN BIT(7) + +#define AD485X_TEST_PAT BIT(2) + +#define AD4858_PACKET_SIZE_20 0 +#define AD4858_PACKET_SIZE_24 1 +#define AD4858_PACKET_SIZE_32 2 + +#define AD4857_PACKET_SIZE_16 0 +#define AD4857_PACKET_SIZE_24 1 + +#define AD485X_TESTPAT_0_DEFAULT 0x2A +#define AD485X_TESTPAT_1_DEFAULT 0x3C +#define AD485X_TESTPAT_2_DEFAULT 0xCE +#define AD485X_TESTPAT_3_DEFAULT(c) (0x0A + (0x10 * (c))) + +#define AD485X_SOFTSPAN_0V_2V5 0 +#define AD485X_SOFTSPAN_N2V5_2V5 1 +#define AD485X_SOFTSPAN_0V_5V 2 +#define AD485X_SOFTSPAN_N5V_5V 3 +#define AD485X_SOFTSPAN_0V_6V25 4 +#define AD485X_SOFTSPAN_N6V25_6V25 5 +#define AD485X_SOFTSPAN_0V_10V 6 +#define AD485X_SOFTSPAN_N10V_10V 7 +#define AD485X_SOFTSPAN_0V_12V5 8 +#define AD485X_SOFTSPAN_N12V5_12V5 9 +#define AD485X_SOFTSPAN_0V_20V 10 +#define AD485X_SOFTSPAN_N20V_20V 11 +#define AD485X_SOFTSPAN_0V_25V 12 +#define AD485X_SOFTSPAN_N25V_25V 13 +#define AD485X_SOFTSPAN_0V_40V 14 +#define AD485X_SOFTSPAN_N40V_40V 15 + +#define AD485X_MAX_LANES 8 +#define AD485X_MAX_IODELAY 32 + +#define AD485X_T_CNVH_NS 40 + +#define AD4858_PROD_ID 0x60 +#define AD4857_PROD_ID 0x61 +#define AD4856_PROD_ID 0x62 +#define AD4855_PROD_ID 0x63 +#define AD4854_PROD_ID 0x64 +#define AD4853_PROD_ID 0x65 +#define AD4852_PROD_ID 0x66 +#define AD4851_PROD_ID 0x67 +#define AD4858I_PROD_ID 0x6F + +enum { + ID_AD4858 = 0, + ID_AD4857, + ID_AD4856, + ID_AD4855, + ID_AD4854, + ID_AD4853, + ID_AD4852, + ID_AD4851, + ID_AD4858I, +}; + +struct ad485x_chip_info { + const char *name; + unsigned int product_id; + const unsigned int (*scale_table)[2]; + int num_scales; + const int *offset_table; + int num_offset; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned long throughput; + unsigned int resolution; +}; + +struct ad485x_state { + struct spi_device *spi; + struct pwm_device *cnv; + struct iio_backend *back; + /* + * Synchronize access to members the of driver state, and ensure + * atomicity of consecutive regmap operations. + */ + struct mutex lock; + struct regmap *regmap; + const struct ad485x_chip_info *info; + unsigned long sampling_freq; + unsigned int (*scales)[2]; + int *offsets; +}; + +static int ad485x_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad485x_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad485x_set_sampling_freq(struct ad485x_state *st, unsigned int freq) +{ + struct pwm_state cnv_state = { + .duty_cycle = AD485X_T_CNVH_NS, + .enabled = true, + }; + int ret; + + if (freq > st->info->throughput) + freq = st->info->throughput; + + cnv_state.period = DIV_ROUND_CLOSEST_ULL(1000000000, freq); + + ret = pwm_apply_state(st->cnv, &cnv_state); + if (ret) + return ret; + + st->sampling_freq = freq; + + return 0; +} + +static int ad485x_setup(struct ad485x_state *st) +{ + unsigned int product_id; + int ret; + + ret = ad485x_set_sampling_freq(st, 1000000); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD485X_REG_INTERFACE_CONFIG_A, + AD485X_SW_RESET); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD485X_REG_INTERFACE_CONFIG_B, + AD485X_SINGLE_INSTRUCTION); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD485X_REG_INTERFACE_CONFIG_A, + AD485X_SDO_ENABLE); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD485X_REG_PRODUCT_ID_L, &product_id); + if (ret) + return ret; + + if (product_id != st->info->product_id) + dev_warn(&st->spi->dev, "Unknown product ID: 0x%02X\n", + product_id); + + ret = regmap_write(st->regmap, AD485X_REG_DEVICE_CTRL, + AD485X_ECHO_CLOCK_MODE); + if (ret) + return ret; + + return regmap_write(st->regmap, AD485X_REG_PACKET, 0); +} + +static int ad485x_find_opt(bool *field, u32 size, u32 *ret_start) +{ + int i, cnt = 0, max_cnt = 0, start, max_start = 0; + + for (i = 0, start = -1; i < size; i++) { + if (field[i] == 0) { + if (start == -1) + start = i; + cnt++; + } else { + if (cnt > max_cnt) { + max_cnt = cnt; + max_start = start; + } + start = -1; + cnt = 0; + } + } + + if (cnt > max_cnt) { + max_cnt = cnt; + max_start = start; + } + + if (!max_cnt) + return -EIO; + + *ret_start = max_start; + + return max_cnt; +} + +static int ad485x_calibrate(struct ad485x_state *st) +{ + int opt_delay, lane_num, delay, i, s, c; + enum iio_backend_interface_type interface_type; + bool pn_status[AD485X_MAX_LANES][AD485X_MAX_IODELAY]; + int ret; + + ret = iio_backend_interface_type_get(st->back, &interface_type); + if (ret) + return ret; + + if (interface_type == IIO_BACKEND_INTERFACE_CMOS) + lane_num = st->info->num_channels; + else + lane_num = 1; + + if (st->info->resolution == 16) { + ret = iio_backend_data_size_set(st->back, 24); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD485X_REG_PACKET, + AD485X_TEST_PAT | AD4857_PACKET_SIZE_24); + if (ret) + return ret; + } else { + ret = iio_backend_data_size_set(st->back, 32); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD485X_REG_PACKET, + AD485X_TEST_PAT | AD4858_PACKET_SIZE_32); + if (ret) + return ret; + } + + for (i = 0; i < st->info->num_channels; i++) { + ret = regmap_write(st->regmap, AD485X_REG_TESTPAT_0(i), + AD485X_TESTPAT_0_DEFAULT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD485X_REG_TESTPAT_1(i), + AD485X_TESTPAT_1_DEFAULT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD485X_REG_TESTPAT_2(i), + AD485X_TESTPAT_2_DEFAULT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD485X_REG_TESTPAT_3(i), + AD485X_TESTPAT_3_DEFAULT(i)); + if (ret) + return ret; + + ret = iio_backend_chan_enable(st->back, i); + if (ret) + return ret; + } + + for (i = 0; i < lane_num; i++) { + for (delay = 0; delay < AD485X_MAX_IODELAY; delay++) { + ret = iio_backend_iodelay_set(st->back, i, delay); + if (ret) + return ret; + ret = iio_backend_chan_status(st->back, i, + &pn_status[i][delay]); + if (ret) + return ret; + } + } + + for (i = 0; i < lane_num; i++) { + c = ad485x_find_opt(&pn_status[i][0], AD485X_MAX_IODELAY, &s); + if (c < 0) + return c; + + opt_delay = s + c / 2; + ret = iio_backend_iodelay_set(st->back, i, opt_delay); + if (ret) + return ret; + } + + for (i = 0; i < st->info->num_channels; i++) { + ret = iio_backend_chan_disable(st->back, i); + if (ret) + return ret; + } + + ret = iio_backend_data_size_set(st->back, 20); + if (ret) + return ret; + + return regmap_write(st->regmap, AD485X_REG_PACKET, 0); +} + +static const char *const ad4858_packet_fmts[] = { + "20-bit", "24-bit", "32-bit" +}; + +static const char *const ad4857_packet_fmts[] = { + "16-bit", "24-bit" +}; + +static int ad485x_set_packet_format(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int format) +{ + struct ad485x_state *st = iio_priv(indio_dev); + unsigned int val; + int ret; + + if (chan->scan_type.realbits == 16) + switch (format) { + case 0: + val = 20; + break; + case 1: + val = 24; + break; + case 2: + val = 32; + break; + default: + return -EINVAL; + } + else if (chan->scan_type.realbits == 20) + switch (format) { + case 0: + val = 16; + break; + case 1: + val = 20; + break; + default: + return -EINVAL; + } + else + return -EINVAL; + + ret = iio_backend_data_size_set(st->back, val); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, AD485X_REG_PACKET, + AD485X_PACKET_FORMAT_MASK, format); +} + +static int ad485x_get_packet_format(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad485x_state *st = iio_priv(indio_dev); + unsigned int format; + int ret; + + ret = regmap_read(st->regmap, AD485X_REG_PACKET, &format); + if (ret) + return ret; + + format &= AD485X_PACKET_FORMAT_MASK; + + return format; +} + +static const struct iio_enum ad4858_packet_fmt = { + .items = ad4858_packet_fmts, + .num_items = ARRAY_SIZE(ad4858_packet_fmts), + .set = ad485x_set_packet_format, + .get = ad485x_get_packet_format, +}; + +static const struct iio_enum ad4857_packet_fmt = { + .items = ad4857_packet_fmts, + .num_items = ARRAY_SIZE(ad4857_packet_fmts), + .set = ad485x_set_packet_format, + .get = ad485x_get_packet_format, +}; + +static int ad485x_get_calibscale(struct ad485x_state *st, int ch, int *val, + int *val2) +{ + unsigned int reg_val; + int gain; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, AD485X_REG_CHX_GAIN_MSB(ch), + ®_val); + if (ret) + return ret; + + gain = (reg_val & 0xFF) << 8; + + ret = regmap_read(st->regmap, AD485X_REG_CHX_GAIN_LSB(ch), + ®_val); + if (ret) + return ret; + + gain |= reg_val & 0xFF; + + *val = gain; + *val2 = 32768; + + return IIO_VAL_FRACTIONAL; +} + +static int ad485x_set_calibscale(struct ad485x_state *st, int ch, int val, + int val2) +{ + unsigned long long gain; + unsigned int reg_val; + int ret; + + gain = val * 1000000 + val2; + gain = gain * 32768; + gain = DIV_U64_ROUND_CLOSEST(gain, 1000000); + + reg_val = gain; + + guard(mutex)(&st->lock); + + ret = regmap_write(st->regmap, AD485X_REG_CHX_GAIN_MSB(ch), + reg_val >> 8); + if (ret) + return ret; + + return regmap_write(st->regmap, AD485X_REG_CHX_GAIN_LSB(ch), + reg_val & 0xFF); +} + +static int ad485x_get_calibbias(struct ad485x_state *st, int ch, int *val, + int *val2) +{ + unsigned int lsb, mid, msb; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, AD485X_REG_CHX_OFFSET_MSB(ch), + &msb); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD485X_REG_CHX_OFFSET_MID(ch), + &mid); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD485X_REG_CHX_OFFSET_LSB(ch), + &lsb); + if (ret) + return ret; + + if (st->info->resolution == 16) { + *val = msb << 8; + *val |= mid; + *val = sign_extend32(*val, 15); + } else { + *val = msb << 12; + *val |= mid << 4; + *val |= lsb >> 4; + *val = sign_extend32(*val, 19); + } + + return IIO_VAL_INT; +} + +static int ad485x_set_calibbias(struct ad485x_state *st, int ch, int val, + int val2) +{ + unsigned int lsb, mid, msb; + int ret; + + if (st->info->resolution == 16) { + lsb = 0; + mid = val & 0xFF; + msb = (val >> 8) & 0xFF; + } else { + lsb = (val << 4) & 0xFF; + mid = (val >> 4) & 0xFF; + msb = (val >> 12) & 0xFF; + } + + guard(mutex)(&st->lock); + + ret = regmap_write(st->regmap, AD485X_REG_CHX_OFFSET_LSB(ch), lsb); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD485X_REG_CHX_OFFSET_MID(ch), mid); + if (ret) + return ret; + + return regmap_write(st->regmap, AD485X_REG_CHX_OFFSET_MSB(ch), msb); +} + +static const unsigned int ad485x_scale_table[][2] = { + {2500, 0x0}, {5000, 0x1}, {5000, 0x2}, {10000, 0x3}, {6250, 0x04}, + {12500, 0x5}, {10000, 0x6}, {20000, 0x7}, {12500, 0x8}, {25000, 0x9}, + {20000, 0xA}, {40000, 0xB}, {25000, 0xC}, {50000, 0xD}, {40000, 0xE}, + {80000, 0xF} +}; + +static const int ad4857_offset_table[] = { + 0, -32768 +}; + +static const int ad4858_offset_table[] = { + 0, -524288 +}; + +static const unsigned int ad485x_scale_avail[] = { + 2500, 5000, 10000, 6250, 12500, 20000, 25000, 40000, 50000, 80000 +}; + +static void __ad485x_get_scale(struct ad485x_state *st, int scale_tbl, + unsigned int *val, unsigned int *val2) +{ + const struct ad485x_chip_info *info = st->info; + const struct iio_chan_spec *chan = &info->channels[0]; + unsigned int tmp; + + tmp = (scale_tbl * 1000000ULL) >> chan->scan_type.realbits; + *val = tmp / 1000000; + *val2 = tmp % 1000000; +} + +static int ad485x_set_scale(struct ad485x_state *st, + const struct iio_chan_spec *chan, int val, int val2) +{ + const struct ad485x_chip_info *info = st->info; + unsigned int scale_val[2]; + unsigned int i, j = 0; + + for (i = 0; i < info->num_scales; i++) { + __ad485x_get_scale(st, info->scale_table[i][0], + &scale_val[0], &scale_val[1]); + if (scale_val[0] != val || scale_val[1] != val2) + continue; + + /* + * Adjust the softspan value (differential or single ended) + * based on the scale value selected and current offset of + * the channel. + * + * If the offset is 0 then continue iterations until finding + * the next matching scale value which always corresponds to + * the single ended mode. + */ + if (!st->offsets[chan->channel] && !j) { + j++; + continue; + } + + return regmap_write(st->regmap, + AD485X_REG_CHX_SOFTSPAN(chan->channel), + info->scale_table[i][1]); + } + + return -EINVAL; +} + +static int ad485x_get_scale(struct ad485x_state *st, + const struct iio_chan_spec *chan, int *val, + int *val2) +{ + const struct ad485x_chip_info *info = st->info; + unsigned int i, softspan_val; + int ret; + + ret = regmap_read(st->regmap, AD485X_REG_CHX_SOFTSPAN(chan->channel), + &softspan_val); + if (ret) + return ret; + + for (i = 0; i < info->num_scales; i++) { + if (softspan_val == info->scale_table[i][1]) + break; + } + + if (i == info->num_scales) + return -EIO; + + __ad485x_get_scale(st, info->scale_table[i][0], val, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ad485x_set_offset(struct ad485x_state *st, + const struct iio_chan_spec *chan, int val) +{ + guard(mutex)(&st->lock); + + if (st->offsets[chan->channel] != val) { + st->offsets[chan->channel] = val; + /* Restore to the default range if offset changes */ + if (st->offsets[chan->channel]) + return regmap_write(st->regmap, + AD485X_REG_CHX_SOFTSPAN(chan->channel), + AD485X_SOFTSPAN_N40V_40V); + return regmap_write(st->regmap, + AD485X_REG_CHX_SOFTSPAN(chan->channel), + AD485X_SOFTSPAN_0V_40V); + } + + return 0; +} + +static int ad485x_scale_offset_fill(struct ad485x_state *st) +{ + unsigned int i, val1, val2; + + st->offsets = devm_kcalloc(&st->spi->dev, st->info->num_channels, + sizeof(*st->offsets), GFP_KERNEL); + if (!st->offsets) + return -ENOMEM; + + st->scales = devm_kmalloc_array(&st->spi->dev, ARRAY_SIZE(ad485x_scale_avail), + sizeof(*st->scales), GFP_KERNEL); + if (!st->scales) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(ad485x_scale_avail); i++) { + __ad485x_get_scale(st, ad485x_scale_avail[i], &val1, &val2); + st->scales[i][0] = val1; + st->scales[i][1] = val2; + } + + return 0; +} + +static int ad485x_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad485x_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->sampling_freq; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + return ad485x_get_calibscale(st, chan->channel, val, val2); + case IIO_CHAN_INFO_SCALE: + return ad485x_get_scale(st, chan, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad485x_get_calibbias(st, chan->channel, val, val2); + case IIO_CHAN_INFO_OFFSET: + scoped_guard(mutex, &st->lock) + *val = st->offsets[chan->channel]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad485x_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad485x_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ad485x_set_sampling_freq(st, val); + case IIO_CHAN_INFO_SCALE: + return ad485x_set_scale(st, chan, val, val2); + case IIO_CHAN_INFO_CALIBSCALE: + return ad485x_set_calibscale(st, chan->channel, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad485x_set_calibbias(st, chan->channel, val, val2); + case IIO_CHAN_INFO_OFFSET: + return ad485x_set_offset(st, chan, val); + default: + return -EINVAL; + } +} + +static int ad485x_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad485x_state *st = iio_priv(indio_dev); + unsigned int c; + int ret; + + for (c = 0; c < st->info->num_channels; c++) { + if (test_bit(c, scan_mask)) + ret = iio_backend_chan_enable(st->back, c); + else + ret = iio_backend_chan_disable(st->back, c); + if (ret) + return ret; + } + + return 0; +} + +static int ad485x_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad485x_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)st->scales; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(ad485x_scale_avail) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OFFSET: + *vals = st->info->offset_table; + *type = IIO_VAL_INT; + *length = st->info->num_offset; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +#define AD485X_IIO_CHANNEL(index, real, storage, info) \ +{ \ + .type = IIO_VOLTAGE, \ + .ext_info = info, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .indexed = 1, \ + .channel = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = real, \ + .storagebits = storage, \ + }, \ +} + +static struct iio_chan_spec_ext_info ad4858_ext_info[] = { + IIO_ENUM("packet_format", IIO_SHARED_BY_ALL, &ad4858_packet_fmt), + IIO_ENUM_AVAILABLE("packet_format", + IIO_SHARED_BY_ALL, &ad4858_packet_fmt), + {}, +}; + +static struct iio_chan_spec_ext_info ad4857_ext_info[] = { + IIO_ENUM("packet_format", IIO_SHARED_BY_ALL, &ad4857_packet_fmt), + IIO_ENUM_AVAILABLE("packet_format", + IIO_SHARED_BY_ALL, &ad4857_packet_fmt), + {}, +}; + +static const struct iio_chan_spec ad4858_channels[] = { + AD485X_IIO_CHANNEL(0, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(1, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(2, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(3, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(4, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(5, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(6, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(7, 20, 32, ad4858_ext_info), +}; + +static const struct iio_chan_spec ad4857_channels[] = { + AD485X_IIO_CHANNEL(0, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(1, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(2, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(3, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(4, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(5, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(6, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(7, 16, 16, ad4857_ext_info), +}; + +static const struct ad485x_chip_info ad4858_info = { + .name = "ad4858", + .product_id = AD4858_PROD_ID, + .scale_table = ad485x_scale_table, + .num_scales = ARRAY_SIZE(ad485x_scale_table), + .offset_table = ad4858_offset_table, + .num_offset = ARRAY_SIZE(ad4858_offset_table), + .channels = ad4858_channels, + .num_channels = ARRAY_SIZE(ad4858_channels), + .throughput = 1 * MEGA, + .resolution = 20, +}; + +static const struct ad485x_chip_info ad4857_info = { + .name = "ad4857", + .product_id = AD4857_PROD_ID, + .scale_table = ad485x_scale_table, + .num_scales = ARRAY_SIZE(ad485x_scale_table), + .offset_table = ad4857_offset_table, + .num_offset = ARRAY_SIZE(ad4857_offset_table), + .channels = ad4857_channels, + .num_channels = ARRAY_SIZE(ad4857_channels), + .throughput = 1 * MEGA, + .resolution = 16, +}; + +static const struct ad485x_chip_info ad4856_info = { + .name = "ad4856", + .product_id = AD4856_PROD_ID, + .scale_table = ad485x_scale_table, + .num_scales = ARRAY_SIZE(ad485x_scale_table), + .offset_table = ad4858_offset_table, + .num_offset = ARRAY_SIZE(ad4858_offset_table), + .channels = ad4858_channels, + .num_channels = ARRAY_SIZE(ad4858_channels), + .throughput = 250 * KILO, + .resolution = 20, + .resolution = 16, +}; + +static const struct ad485x_chip_info ad4855_info = { + .name = "ad4855", + .product_id = AD4855_PROD_ID, + .scale_table = ad485x_scale_table, + .num_scales = ARRAY_SIZE(ad485x_scale_table), + .offset_table = ad4857_offset_table, + .num_offset = ARRAY_SIZE(ad4857_offset_table), + .channels = ad4857_channels, + .num_channels = ARRAY_SIZE(ad4857_channels), + .throughput = 250 * KILO, + .resolution = 16, +}; + +static const struct ad485x_chip_info ad4854_info = { + .name = "ad4854", + .product_id = AD4854_PROD_ID, + .scale_table = ad485x_scale_table, + .num_scales = ARRAY_SIZE(ad485x_scale_table), + .offset_table = ad4858_offset_table, + .num_offset = ARRAY_SIZE(ad4858_offset_table), + .channels = ad4858_channels, + .num_channels = ARRAY_SIZE(ad4858_channels), + .throughput = 1 * MEGA, + .resolution = 20, +}; + +static const struct ad485x_chip_info ad4853_info = { + .name = "ad4853", + .product_id = AD4853_PROD_ID, + .scale_table = ad485x_scale_table, + .num_scales = ARRAY_SIZE(ad485x_scale_table), + .offset_table = ad4857_offset_table, + .num_offset = ARRAY_SIZE(ad4857_offset_table), + .channels = ad4857_channels, + .num_channels = ARRAY_SIZE(ad4857_channels), + .throughput = 1 * MEGA, + .resolution = 16, +}; + +static const struct ad485x_chip_info ad4852_info = { + .name = "ad4852", + .product_id = AD4852_PROD_ID, + .scale_table = ad485x_scale_table, + .num_scales = ARRAY_SIZE(ad485x_scale_table), + .offset_table = ad4858_offset_table, + .num_offset = ARRAY_SIZE(ad4858_offset_table), + .channels = ad4858_channels, + .num_channels = ARRAY_SIZE(ad4858_channels), + .throughput = 250 * KILO, + .resolution = 20, +}; + +static const struct ad485x_chip_info ad4851_info = { + .name = "ad4851", + .product_id = AD4851_PROD_ID, + .scale_table = ad485x_scale_table, + .num_scales = ARRAY_SIZE(ad485x_scale_table), + .offset_table = ad4857_offset_table, + .num_offset = ARRAY_SIZE(ad4857_offset_table), + .channels = ad4857_channels, + .num_channels = ARRAY_SIZE(ad4857_channels), + .throughput = 250 * KILO, + .resolution = 16, +}; + +static const struct ad485x_chip_info ad4858i_info = { + .name = "ad4858i", + .product_id = AD4858I_PROD_ID, + .scale_table = ad485x_scale_table, + .num_scales = ARRAY_SIZE(ad485x_scale_table), + .offset_table = ad4858_offset_table, + .num_offset = ARRAY_SIZE(ad4858_offset_table), + .channels = ad4858_channels, + .num_channels = ARRAY_SIZE(ad4858_channels), + .throughput = 1000000, + .resolution = 20, +}; + +static const struct iio_info ad485x_info = { + .debugfs_reg_access = ad485x_reg_access, + .read_raw = ad485x_read_raw, + .write_raw = ad485x_write_raw, + .update_scan_mode = ad485x_update_scan_mode, + .read_avail = ad485x_read_avail, +}; + +static const struct regmap_config regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .read_flag_mask = BIT(7), +}; + +static const char * const ad485x_power_supplies[] = { + "vcc", "vdd", "vddh", "vio" +}; + +static int ad485x_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad485x_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + mutex_init(&st->lock); + + ret = devm_regulator_bulk_get_enable(&spi->dev, + ARRAY_SIZE(ad485x_power_supplies), + ad485x_power_supplies); + if (ret) + return dev_err_probe(&spi->dev, ret, + "failed to get and enable supplies\n"); + + st->cnv = devm_pwm_get(&spi->dev, NULL); + if (IS_ERR(st->cnv)) + return PTR_ERR(st->cnv); + + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + + st->regmap = devm_regmap_init_spi(spi, ®map_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = ad485x_scale_offset_fill(st); + if (ret) + return ret; + + ret = ad485x_setup(st); + if (ret) + return ret; + + indio_dev->name = st->info->name; + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; + indio_dev->info = &ad485x_info; + + st->back = devm_iio_backend_get(&spi->dev, NULL); + if (IS_ERR(st->back)) + return PTR_ERR(st->back); + + ret = devm_iio_backend_request_buffer(&spi->dev, st->back, indio_dev); + if (ret) + return ret; + + ret = devm_iio_backend_enable(&spi->dev, st->back); + if (ret) + return ret; + + ret = ad485x_calibrate(st); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ad485x_of_match[] = { + { .compatible = "adi,ad4858", .data = &ad4858_info, }, + { .compatible = "adi,ad4857", .data = &ad4857_info, }, + { .compatible = "adi,ad4856", .data = &ad4856_info, }, + { .compatible = "adi,ad4855", .data = &ad4855_info, }, + { .compatible = "adi,ad4854", .data = &ad4854_info, }, + { .compatible = "adi,ad4853", .data = &ad4853_info, }, + { .compatible = "adi,ad4852", .data = &ad4852_info, }, + { .compatible = "adi,ad4851", .data = &ad4851_info, }, + { .compatible = "adi,ad4858i", .data = &ad4858i_info, }, + {} +}; + +static const struct spi_device_id ad485x_spi_id[] = { + { "ad4858", (kernel_ulong_t)&ad4858_info }, + { "ad4857", (kernel_ulong_t)&ad4857_info }, + { "ad4856", (kernel_ulong_t)&ad4856_info }, + { "ad4855", (kernel_ulong_t)&ad4855_info }, + { "ad4854", (kernel_ulong_t)&ad4854_info }, + { "ad4853", (kernel_ulong_t)&ad4853_info }, + { "ad4852", (kernel_ulong_t)&ad4852_info }, + { "ad4851", (kernel_ulong_t)&ad4851_info }, + { "ad4858i", (kernel_ulong_t)&ad4858i_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad485x_spi_id); + +static struct spi_driver ad485x_driver = { + .probe = ad485x_probe, + .driver = { + .name = "ad485x", + .of_match_table = ad485x_of_match, + }, + .id_table = ad485x_spi_id, +}; +module_spi_driver(ad485x_driver); + +MODULE_AUTHOR("Sergiu Cuciurean "); +MODULE_AUTHOR("Dragos Bogdan "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices AD485x DAS driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_BACKEND); diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 0cf0d81358fd58..3efba7f20f50b7 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -39,9 +39,14 @@ #define ADI_AXI_REG_RSTN_MMCM_RSTN BIT(1) #define ADI_AXI_REG_RSTN_RSTN BIT(0) +#define ADI_AXI_ADC_REG_CONFIG 0x000c +#define ADI_AXI_ADC_REG_CONFIG_CMOS_OR_LVDS_N BIT(7) + #define ADI_AXI_ADC_REG_CTRL 0x0044 #define ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK BIT(1) +#define ADI_AXI_ADC_REG_CNTRL_3 0x004c + /* ADC Channel controls */ #define ADI_AXI_REG_CHAN_CTRL(c) (0x0400 + (c) * 0x40) @@ -233,6 +238,43 @@ static int axi_adc_chan_disable(struct iio_backend *back, unsigned int chan) ADI_AXI_REG_CHAN_CTRL_ENABLE); } +static int axi_adc_interface_type_get(struct iio_backend *back, + enum iio_backend_interface_type *type) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, ADI_AXI_ADC_REG_CONFIG, &val); + if (ret) + return ret; + + if (val & ADI_AXI_ADC_REG_CONFIG_CMOS_OR_LVDS_N) + *type = IIO_BACKEND_INTERFACE_CMOS; + else + *type = IIO_BACKEND_INTERFACE_LVDS; + + return 0; +} + +static int axi_adc_data_size_set(struct iio_backend *back, + ssize_t size) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + unsigned int val; + + if (size <= 20) + val = 0; + else if (size <= 24) + val = 1; + else if (size <= 32) + val = 3; + else + return -EINVAL; + + return regmap_write(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, val); +} + static struct iio_buffer *axi_adc_request_buffer(struct iio_backend *back, struct iio_dev *indio_dev) { @@ -269,6 +311,8 @@ static const struct iio_backend_ops adi_axi_adc_generic = { .iodelay_set = axi_adc_iodelays_set, .test_pattern_set = axi_adc_test_pattern_set, .chan_status = axi_adc_chan_status, + .interface_type_get = axi_adc_interface_type_get, + .data_size_set = axi_adc_data_size_set, }; static int adi_axi_adc_probe(struct platform_device *pdev) diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c index 929aff4040edd2..b7de1c15c26060 100644 --- a/drivers/iio/industrialio-backend.c +++ b/drivers/iio/industrialio-backend.c @@ -449,6 +449,51 @@ ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private, } EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_set, IIO_BACKEND); +/** + * iio_backend_interface_type_get - get the interace type used. + * @back: Backend device + * @type: Interface type + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_interface_type_get(struct iio_backend *back, + enum iio_backend_interface_type *type) +{ + int ret; + + ret = iio_backend_op_call(back, interface_type_get, type); + if (ret) + return ret; + + if (*type > IIO_BACKEND_INTERFACE_CMOS) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_backend_interface_type_get, IIO_BACKEND); + +/** + * iio_backend_data_size_set - set the data width/size in the data bus. + * @back: Backend device + * @size: Size in bits + * + * Some frontend devices can dynamically control the word/data size on the + * interface/data bus. Hence, the backend device needs to be aware of it so + * data can be correctly transferred. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_size_set(struct iio_backend *back, ssize_t size) +{ + if (!size) + return -EINVAL; + + return iio_backend_op_call(back, data_size_set, size); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_size_set, IIO_BACKEND); + /** * iio_backend_extend_chan_spec - Extend an IIO channel * @indio_dev: IIO device diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 90913519f11a85..e6ce84bdda9ba0 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_PWM) += core.o +obj-$(CONFIG_PWM) += core.o pwm-axi-pwmgen.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_APPLE) += pwm-apple.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o diff --git a/drivers/pwm/pwm-axi-pwmgen.c b/drivers/pwm/pwm-axi-pwmgen.c new file mode 100644 index 00000000000000..ce9c5c001a4363 --- /dev/null +++ b/drivers/pwm/pwm-axi-pwmgen.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AXI PWM generator + * + * Copyright 2020 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AXI_PWMGEN_VERSION_MAJOR(x) (((x) >> 16) & 0xff) +#define AXI_PWMGEN_VERSION_MINOR(x) (((x) >> 8) & 0xff) +#define AXI_PWMGEN_VERSION_PATCH(x) ((x) & 0xff) + +#define AXI_PWMGEN_REG_CORE_VERSION 0x00 +#define AXI_PWMGEN_REG_ID 0x04 +#define AXI_PWMGEN_REG_SCRATCHPAD 0x08 +#define AXI_PWMGEN_REG_CORE_MAGIC 0x0C +#define AXI_PWMGEN_REG_CONFIG 0x10 +#define AXI_PWMGEN_REG_NPWM 0x14 +/* register layout is a bit different between v1 and v2 HDL */ +#define AXI_PWMGEN_V1_CHX_PERIOD(ch) (0x40 + 12 * (ch)) +#define AXI_PWMGEN_V1_CHX_DUTY(ch) (0x44 + 12 * (ch)) +#define AXI_PWMGEN_V1_CHX_PHASE(ch) (0x48 + 12 * (ch)) +#define AXI_PWMGEN_V2_CHX_PERIOD(ch) (0x40 + 4 * (ch)) +#define AXI_PWMGEN_V2_CHX_DUTY(ch) (0x80 + 4 * (ch)) +#define AXI_PWMGEN_V2_CHX_PHASE(ch) (0xC0 + 4 * (ch)) +#define AXI_PWMGEN_CHX_PERIOD(p, ch) \ + ((p)->hw_maj_ver == 1 ? AXI_PWMGEN_V1_CHX_PERIOD(ch) : AXI_PWMGEN_V2_CHX_PERIOD(ch)) +#define AXI_PWMGEN_CHX_DUTY(p, ch) \ + ((p)->hw_maj_ver == 1 ? AXI_PWMGEN_V1_CHX_DUTY(ch) : AXI_PWMGEN_V2_CHX_DUTY(ch)) +#define AXI_PWMGEN_CHX_PHASE(p, ch) \ + ((p)->hw_maj_ver == 1 ? AXI_PWMGEN_V1_CHX_PHASE(ch) : AXI_PWMGEN_V2_CHX_PHASE(ch)) +#define AXI_PWMGEN_TEST_DATA 0x5A0F0081 +#define AXI_PWMGEN_LOAD_CONIG BIT(1) +#define AXI_PWMGEN_RESET BIT(0) + +#define AXI_PWMGEN_PSEC_PER_SEC 1000000000000ULL +#define AXI_PWMGEN_N_MAX_PWMS 16 + +static const unsigned long long axi_pwmgen_scale = 1000ULL; // old PWM_UNIT_NSEC + +struct axi_pwmgen { + struct clk *clk; + void __iomem *base; + u8 hw_maj_ver; + + /* Used to store the period when the channel is disabled */ + unsigned int ch_period[AXI_PWMGEN_N_MAX_PWMS]; +}; + +static inline struct axi_pwmgen *axi_pwmgen_from_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static inline unsigned int axi_pwmgen_read(struct axi_pwmgen *pwm, + unsigned int reg) +{ + return readl(pwm->base + reg); +} + +static inline void axi_pwmgen_write(struct axi_pwmgen *pwm, + unsigned int reg, + unsigned int value) +{ + writel(value, pwm->base + reg); +} + +static void axi_pwmgen_write_mask(struct axi_pwmgen *pwm, + unsigned int reg, + unsigned int mask, + unsigned int value) +{ + unsigned int temp; + + temp = axi_pwmgen_read(pwm, reg); + axi_pwmgen_write(pwm, reg, (temp & ~mask) | value); +} + +static int axi_pwmgen_apply(struct pwm_chip *chip, struct pwm_device *device, + const struct pwm_state *state) +{ + unsigned long long rate, clk_period_ps, target, cnt; + unsigned int ch = device->hwpwm; + struct axi_pwmgen *pwm; + + pwm = axi_pwmgen_from_chip(chip); + rate = clk_get_rate(pwm->clk); + clk_period_ps = DIV_ROUND_CLOSEST_ULL(AXI_PWMGEN_PSEC_PER_SEC, rate); + + target = state->period * axi_pwmgen_scale; + cnt = target ? DIV_ROUND_CLOSEST_ULL(target, clk_period_ps) : 0; + pwm->ch_period[ch] = cnt; + axi_pwmgen_write(pwm, AXI_PWMGEN_CHX_PERIOD(pwm, ch), + state->enabled ? pwm->ch_period[ch] : 0); + + target = state->duty_cycle * axi_pwmgen_scale; + cnt = target ? DIV_ROUND_CLOSEST_ULL(target, clk_period_ps) : 0; + axi_pwmgen_write(pwm, AXI_PWMGEN_CHX_DUTY(pwm, ch), cnt); + + axi_pwmgen_write(pwm, AXI_PWMGEN_CHX_PHASE(pwm, ch), 0); + + /* Apply the new config */ + axi_pwmgen_write(pwm, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONIG); + + return 0; +} + +static int axi_pwmgen_capture(struct pwm_chip *chip, struct pwm_device *device, + struct pwm_capture *capture, + unsigned long timeout __always_unused) +{ + struct axi_pwmgen *pwmgen = axi_pwmgen_from_chip(chip); + unsigned long long rate, cnt, clk_period_ps; + unsigned int ch = device->hwpwm; + + rate = clk_get_rate(pwmgen->clk); + if (!rate) + return -EINVAL; + + clk_period_ps = DIV_ROUND_CLOSEST_ULL(AXI_PWMGEN_PSEC_PER_SEC, rate); + cnt = axi_pwmgen_read(pwmgen, AXI_PWMGEN_CHX_PERIOD(pwmgen, ch)); + cnt *= clk_period_ps; + if (cnt) + capture->period = DIV_ROUND_CLOSEST_ULL(cnt, + axi_pwmgen_scale); + else + capture->period = 0; + cnt = axi_pwmgen_read(pwmgen, AXI_PWMGEN_CHX_DUTY(pwmgen, ch)); + cnt *= clk_period_ps; + if (cnt) + capture->duty_cycle = DIV_ROUND_CLOSEST_ULL(cnt, + axi_pwmgen_scale); + else + capture->duty_cycle = 0; + + return 0; +} + +static int axi_pwmgen_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pwm_capture capture; + int ret; + + ret = axi_pwmgen_capture(chip, pwm, &capture, 0); + if (ret < 0) + return ret; + + state->enabled = state; + state->period = capture.period; + state->duty_cycle = capture.duty_cycle; + + return 0; +} + +static const struct pwm_ops axi_pwmgen_pwm_ops = { + .apply = axi_pwmgen_apply, + .capture = axi_pwmgen_capture, + .get_state = axi_pwmgen_get_state, +}; + +static const struct of_device_id axi_pwmgen_ids[] = { + { + .compatible = "adi,axi-pwmgen", + }, + { } +}; +MODULE_DEVICE_TABLE(of, axi_pwmgen_ids); + +static int axi_pwmgen_setup(struct pwm_chip *chip) +{ + struct axi_pwmgen *pwm; + unsigned int reg; + int idx; + + pwm = axi_pwmgen_from_chip(chip); + axi_pwmgen_write(pwm, AXI_PWMGEN_REG_SCRATCHPAD, AXI_PWMGEN_TEST_DATA); + reg = axi_pwmgen_read(pwm, AXI_PWMGEN_REG_SCRATCHPAD); + if (reg != AXI_PWMGEN_TEST_DATA) { + dev_err(&chip->dev, "failed to access the device registers\n"); + return -EIO; + } + + reg = axi_pwmgen_read(pwm, AXI_PWMGEN_REG_CORE_VERSION); + pwm->hw_maj_ver = AXI_PWMGEN_VERSION_MAJOR(reg); + + if (pwm->hw_maj_ver != 1 && pwm->hw_maj_ver != 2) { + dev_err(&chip->dev, "Unsupported peripheral version %u.%u.%u\n", + AXI_PWMGEN_VERSION_MAJOR(reg), + AXI_PWMGEN_VERSION_MINOR(reg), + AXI_PWMGEN_VERSION_PATCH(reg)); + return -ENODEV; + } + + chip->npwm = axi_pwmgen_read(pwm, AXI_PWMGEN_REG_NPWM); + if (chip->npwm > AXI_PWMGEN_N_MAX_PWMS) + return -EINVAL; + + /* Disable all the outputs */ + for (idx = 0; idx < chip->npwm; idx++) { + axi_pwmgen_write(pwm, AXI_PWMGEN_CHX_PERIOD(pwm, idx), 0); + axi_pwmgen_write(pwm, AXI_PWMGEN_CHX_DUTY(pwm, idx), 0); + axi_pwmgen_write(pwm, AXI_PWMGEN_CHX_PHASE(pwm, idx), 0); + } + + /* Enable the core */ + axi_pwmgen_write_mask(pwm, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_RESET, 0); + + return 0; +} + +static void axi_pwmgen_clk_disable(void *data) +{ + clk_disable_unprepare(data); +} + + + +static int axi_pwmgen_probe(struct platform_device *pdev) +{ + struct axi_pwmgen *pwm; + struct pwm_chip *chip; + struct resource *mem; + int ret; + + chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pwm)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + pwm = axi_pwmgen_from_chip(chip); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pwm->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(pwm->base)) + return PTR_ERR(pwm->base); + + pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) + return PTR_ERR(pwm->clk); + + ret = clk_prepare_enable(pwm->clk); + if (ret) + return ret; + ret = devm_add_action_or_reset(&pdev->dev, axi_pwmgen_clk_disable, + pwm->clk); + if (ret) + return ret; + + chip->ops = &axi_pwmgen_pwm_ops; + + ret = axi_pwmgen_setup(chip); + if (ret < 0) + return ret; + + return devm_pwmchip_add(&pdev->dev, chip); +} + +static struct platform_driver axi_pwmgen_driver = { + .driver = { + .name = "adi,axi-pwmgen", + .of_match_table = axi_pwmgen_ids, + }, + .probe = axi_pwmgen_probe, +}; +module_platform_driver(axi_pwmgen_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sergiu Cuciurean "); +MODULE_DESCRIPTION("Driver for the Analog Devices AXI PWM generator"); diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h index 8099759d724280..1f4abe3547bae7 100644 --- a/include/linux/iio/backend.h +++ b/include/linux/iio/backend.h @@ -63,6 +63,11 @@ enum iio_backend_sample_trigger { IIO_BACKEND_SAMPLE_TRIGGER_MAX }; +enum iio_backend_interface_type { + IIO_BACKEND_INTERFACE_LVDS, + IIO_BACKEND_INTERFACE_CMOS +}; + /** * struct iio_backend_ops - operations structure for an iio_backend * @enable: Enable backend. @@ -81,6 +86,8 @@ enum iio_backend_sample_trigger { * @extend_chan_spec: Extend an IIO channel. * @ext_info_set: Extended info setter. * @ext_info_get: Extended info getter. + * @interface_type_get: Interface type. + * @data_size_set: Data size. **/ struct iio_backend_ops { int (*enable)(struct iio_backend *back); @@ -113,6 +120,9 @@ struct iio_backend_ops { const char *buf, size_t len); int (*ext_info_get)(struct iio_backend *back, uintptr_t private, const struct iio_chan_spec *chan, char *buf); + int (*interface_type_get)(struct iio_backend *back, + enum iio_backend_interface_type *type); + int (*data_size_set)(struct iio_backend *back, ssize_t size); }; int iio_backend_chan_enable(struct iio_backend *back, unsigned int chan); @@ -142,6 +152,9 @@ ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private, ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private, const struct iio_chan_spec *chan, char *buf); +int iio_backend_interface_type_get(struct iio_backend *back, + enum iio_backend_interface_type *type); +int iio_backend_data_size_set(struct iio_backend *back, ssize_t size); int iio_backend_extend_chan_spec(struct iio_dev *indio_dev, struct iio_backend *back, struct iio_chan_spec *chan);